content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  package org.apache.cayenne.dbsync.naming;
  
  import org.apache.cayenne.map.DbAttribute;
  import org.apache.cayenne.map.DbEntity;
  import org.apache.cayenne.map.DbJoin;
  import org.apache.cayenne.map.DbRelationship;
  import org.apache.cayenne.util.Util;
  import org.jvnet.inflector.Noun;
  
  import java.util.List;
  import java.util.Locale;
  import java.util.Objects;
  
  /**
   * The default strategy for converting DB-layer to Object-layer names.
   *
   * @since 4.0
   */
  public class DefaultObjectNameGenerator implements ObjectNameGenerator {
  
      private DbEntityNameStemmer dbEntityNameStemmer;
  
      public DefaultObjectNameGenerator() {
          this.dbEntityNameStemmer = NoStemStemmer.getInstance();
      }
  
      public DefaultObjectNameGenerator(DbEntityNameStemmer dbEntityNameStemmer) {
          this.dbEntityNameStemmer = dbEntityNameStemmer;
      }
  
      @Override
      public String relationshipName(DbRelationship... relationshipChain) {
  
          if (relationshipChain == null || relationshipChain.length < 1) {
              throw new IllegalArgumentException("At least on relationship is expected: " + relationshipChain);
          }
  
          // ignore the name of DbRelationship itself (FWIW we may be generating a new name for it here)...
          // generate the name based on join semantics...
  
          String name = isToMany(relationshipChain)
                  ? toManyRelationshipName(relationshipChain)
                  : toOneRelationshipName(relationshipChain);
  
          return Util.underscoredToJava(name, false);
      }
  
      protected boolean isToMany(DbRelationship... relationshipChain) {
  
          for (DbRelationship r : relationshipChain) {
              if (r.isToMany()) {
                  return true;
              }
          }
  
          return false;
      }
  
      protected String stemmed(String dbEntityName) {
          return dbEntityNameStemmer.stem(Objects.requireNonNull(dbEntityName));
      }
  
      protected String toManyRelationshipName(DbRelationship... relationshipChain) {
  
          DbRelationship last = relationshipChain[relationshipChain.length - 1];
  
          String baseName = stemmed(last.getTargetEntityName());
  
          try {
              // by default we use English rules here...
              return Noun.pluralOf(baseName.toLowerCase(), Locale.ENGLISH);
          } catch (Exception inflectorError) {
              //  seems that Inflector cannot be trusted. For instance, it
              // throws an exception when invoked for word "ADDRESS" (although
              // lower case works fine). To feel safe, we use superclass'
              // behavior if something's gone wrong
              return baseName;
          }
      }
  
      protected String toOneRelationshipName(DbRelationship... relationshipChain) {
  
          DbRelationship first = relationshipChain[0];
          DbRelationship last = relationshipChain[relationshipChain.length - 1];
  
          List<DbJoin> joins = first.getJoins();
          if (joins.isEmpty()) {
              // In case, when uses EditRelationship button, relationship doesn't exist => it doesn't have joins
              // and just return targetName
              return stemmed(last.getTargetEntityName());
          }
  
          DbJoin join1 = joins.get(0);
  
          // TODO: multi-join relationships
  
          // return the name of the FK column sans ID
          String fkColName = join1.getSourceName();
          if (fkColName == null) {
              return stemmed(last.getTargetEntityName());
          } else if (fkColName.toUpperCase().endsWith("_ID") && fkColName.length() > 3) {
              return fkColName.substring(0, fkColName.length() - 3);
          } else if (fkColName.toUpperCase().endsWith("ID") && fkColName.length() > 2) {
              return fkColName.substring(0, fkColName.length() - 2);
          } else {
              return stemmed(last.getTargetEntityName());
          }
      }
  
      @Override
      public String objEntityName(DbEntity dbEntity) {
          String baseName = stemmed(dbEntity.getName());
          return Util.underscoredToJava(baseName, true);
      }
  
      @Override
      public String objAttributeName(DbAttribute attr) {
          return Util.underscoredToJava(attr.getName(), false);
      }
  }
  