// Due to @AnnotationOverride overriding rules, I don't want the constructor to be public
 // TODO get rid of it and use setters
 private Ejb3JoinColumn(
     String sqlType,
     String name,
     boolean nullable,
     boolean unique,
     boolean insertable,
     boolean updatable,
     String referencedColumn,
     String secondaryTable,
     Map<String, Join> joins,
     PropertyHolder propertyHolder,
     String propertyName,
     String mappedBy,
     boolean isImplicit,
     MetadataBuildingContext buildingContext) {
   super();
   setImplicit(isImplicit);
   setSqlType(sqlType);
   setLogicalColumnName(name);
   setNullable(nullable);
   setUnique(unique);
   setInsertable(insertable);
   setUpdatable(updatable);
   setExplicitTableName(secondaryTable);
   setPropertyHolder(propertyHolder);
   setJoins(joins);
   setBuildingContext(buildingContext);
   setPropertyName(BinderHelper.getRelativePath(propertyHolder, propertyName));
   bind();
   this.referencedColumn = referencedColumn;
   this.mappedBy = mappedBy;
 }
 /** build join column for SecondaryTables */
 private static Ejb3JoinColumn buildJoinColumn(
     JoinColumn ann,
     String mappedBy,
     Map<String, Join> joins,
     PropertyHolder propertyHolder,
     String propertyName,
     String suffixForDefaultColumnName,
     MetadataBuildingContext buildingContext) {
   if (ann != null) {
     if (BinderHelper.isEmptyAnnotationValue(mappedBy)) {
       throw new AnnotationException(
           "Illegal attempt to define a @JoinColumn with a mappedBy association: "
               + BinderHelper.getRelativePath(propertyHolder, propertyName));
     }
     Ejb3JoinColumn joinColumn = new Ejb3JoinColumn();
     joinColumn.setBuildingContext(buildingContext);
     joinColumn.setJoinAnnotation(ann, null);
     if (StringHelper.isEmpty(joinColumn.getLogicalColumnName())
         && !StringHelper.isEmpty(suffixForDefaultColumnName)) {
       joinColumn.setLogicalColumnName(propertyName + suffixForDefaultColumnName);
     }
     joinColumn.setJoins(joins);
     joinColumn.setPropertyHolder(propertyHolder);
     joinColumn.setPropertyName(BinderHelper.getRelativePath(propertyHolder, propertyName));
     joinColumn.setImplicit(false);
     joinColumn.bind();
     return joinColumn;
   } else {
     Ejb3JoinColumn joinColumn = new Ejb3JoinColumn();
     joinColumn.setMappedBy(mappedBy);
     joinColumn.setJoins(joins);
     joinColumn.setPropertyHolder(propertyHolder);
     joinColumn.setPropertyName(BinderHelper.getRelativePath(propertyHolder, propertyName));
     // property name + suffix is an "explicit" column name
     if (!StringHelper.isEmpty(suffixForDefaultColumnName)) {
       joinColumn.setLogicalColumnName(propertyName + suffixForDefaultColumnName);
       joinColumn.setImplicit(false);
     } else {
       joinColumn.setImplicit(true);
     }
     joinColumn.setBuildingContext(buildingContext);
     joinColumn.bind();
     return joinColumn;
   }
 }
  public static Ejb3JoinColumn[] buildJoinTableJoinColumns(
      JoinColumn[] annJoins,
      Map<String, Join> secondaryTables,
      PropertyHolder propertyHolder,
      String propertyName,
      String mappedBy,
      MetadataBuildingContext buildingContext) {
    Ejb3JoinColumn[] joinColumns;
    if (annJoins == null) {
      Ejb3JoinColumn currentJoinColumn = new Ejb3JoinColumn();
      currentJoinColumn.setImplicit(true);
      currentJoinColumn.setNullable(false); // I break the spec, but it's for good
      currentJoinColumn.setPropertyHolder(propertyHolder);
      currentJoinColumn.setJoins(secondaryTables);
      currentJoinColumn.setBuildingContext(buildingContext);
      currentJoinColumn.setPropertyName(BinderHelper.getRelativePath(propertyHolder, propertyName));
      currentJoinColumn.setMappedBy(mappedBy);
      currentJoinColumn.bind();

      joinColumns = new Ejb3JoinColumn[] {currentJoinColumn};

    } else {
      joinColumns = new Ejb3JoinColumn[annJoins.length];
      JoinColumn annJoin;
      int length = annJoins.length;
      for (int index = 0; index < length; index++) {
        annJoin = annJoins[index];
        Ejb3JoinColumn currentJoinColumn = new Ejb3JoinColumn();
        currentJoinColumn.setImplicit(true);
        currentJoinColumn.setPropertyHolder(propertyHolder);
        currentJoinColumn.setJoins(secondaryTables);
        currentJoinColumn.setBuildingContext(buildingContext);
        currentJoinColumn.setPropertyName(
            BinderHelper.getRelativePath(propertyHolder, propertyName));
        currentJoinColumn.setMappedBy(mappedBy);
        currentJoinColumn.setJoinAnnotation(annJoin, propertyName);
        currentJoinColumn.setNullable(false); // I break the spec, but it's for good
        // done afterQuery the annotation to override it
        currentJoinColumn.bind();
        joinColumns[index] = currentJoinColumn;
      }
    }
    return joinColumns;
  }
  // TODO default name still useful in association table
  public void setJoinAnnotation(JoinColumn annJoin, String defaultName) {
    if (annJoin == null) {
      setImplicit(true);
    } else {
      setImplicit(false);
      if (!BinderHelper.isEmptyAnnotationValue(annJoin.columnDefinition())) {
        setSqlType(
            getBuildingContext()
                .getObjectNameNormalizer()
                .applyGlobalQuoting(annJoin.columnDefinition()));
      }
      if (!BinderHelper.isEmptyAnnotationValue(annJoin.name())) {
        setLogicalColumnName(annJoin.name());
      }
      setNullable(annJoin.nullable());
      setUnique(annJoin.unique());
      setInsertable(annJoin.insertable());
      setUpdatable(annJoin.updatable());
      setReferencedColumn(annJoin.referencedColumnName());

      if (BinderHelper.isEmptyAnnotationValue(annJoin.table())) {
        setExplicitTableName("");
      } else {
        final Identifier logicalIdentifier =
            getBuildingContext().getMetadataCollector().getDatabase().toIdentifier(annJoin.table());
        final Identifier physicalIdentifier =
            getBuildingContext()
                .getBuildingOptions()
                .getPhysicalNamingStrategy()
                .toPhysicalTableName(
                    logicalIdentifier,
                    getBuildingContext().getMetadataCollector().getDatabase().getJdbcEnvironment());
        setExplicitTableName(
            physicalIdentifier.render(
                getBuildingContext().getMetadataCollector().getDatabase().getDialect()));
      }
    }
  }
 /** build join formula */
 public static Ejb3JoinColumn buildJoinFormula(
     JoinFormula ann,
     String mappedBy,
     Map<String, Join> joins,
     PropertyHolder propertyHolder,
     String propertyName,
     MetadataBuildingContext buildingContext) {
   Ejb3JoinColumn formulaColumn = new Ejb3JoinColumn();
   formulaColumn.setFormula(ann.value());
   formulaColumn.setReferencedColumn(ann.referencedColumnName());
   formulaColumn.setBuildingContext(buildingContext);
   formulaColumn.setPropertyHolder(propertyHolder);
   formulaColumn.setJoins(joins);
   formulaColumn.setPropertyName(BinderHelper.getRelativePath(propertyHolder, propertyName));
   formulaColumn.bind();
   return formulaColumn;
 }
  @SuppressWarnings({"unchecked"})
  public void doSecondPass(Map persistentClasses) throws MappingException {
    PersistentClass referencedPersistentClass =
        (PersistentClass) persistentClasses.get(referencedEntityName);
    // TODO better error names
    if (referencedPersistentClass == null) {
      throw new AnnotationException("Unknown entity name: " + referencedEntityName);
    }
    if (!(referencedPersistentClass.getIdentifier() instanceof Component)) {
      throw new AssertionFailure(
          "Unexpected identifier type on the referenced entity when mapping a @MapsId: "
              + referencedEntityName);
    }
    Component referencedComponent = (Component) referencedPersistentClass.getIdentifier();
    Iterator<Property> properties = referencedComponent.getPropertyIterator();

    // prepare column name structure
    boolean isExplicitReference = true;
    Map<String, Ejb3JoinColumn> columnByReferencedName =
        new HashMap<String, Ejb3JoinColumn>(joinColumns.length);
    for (Ejb3JoinColumn joinColumn : joinColumns) {
      final String referencedColumnName = joinColumn.getReferencedColumn();
      if (referencedColumnName == null
          || BinderHelper.isEmptyAnnotationValue(referencedColumnName)) {
        break;
      }
      // JPA 2 requires referencedColumnNames to be case insensitive
      columnByReferencedName.put(referencedColumnName.toLowerCase(), joinColumn);
    }
    // try default column orientation
    int index = 0;
    if (columnByReferencedName.isEmpty()) {
      isExplicitReference = false;
      for (Ejb3JoinColumn joinColumn : joinColumns) {
        columnByReferencedName.put("" + index, joinColumn);
        index++;
      }
      index = 0;
    }

    while (properties.hasNext()) {
      Property referencedProperty = properties.next();
      if (referencedProperty.isComposite()) {
        throw new AssertionFailure(
            "Unexpected nested component on the referenced entity when mapping a @MapsId: "
                + referencedEntityName);
      } else {
        Property property = new Property();
        property.setName(referencedProperty.getName());
        property.setNodeName(referencedProperty.getNodeName());
        // FIXME set optional?
        // property.setOptional( property.isOptional() );
        property.setPersistentClass(component.getOwner());
        property.setPropertyAccessorName(referencedProperty.getPropertyAccessorName());
        SimpleValue value = new SimpleValue(mappings, component.getTable());
        property.setValue(value);
        final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
        value.setTypeName(referencedValue.getTypeName());
        value.setTypeParameters(referencedValue.getTypeParameters());
        final Iterator<Column> columns = referencedValue.getColumnIterator();

        if (joinColumns[0].isNameDeferred()) {
          joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
              referencedPersistentClass, columns, value);
        } else {
          // FIXME take care of Formula
          while (columns.hasNext()) {
            Column column = columns.next();
            final Ejb3JoinColumn joinColumn;
            String logicalColumnName = null;
            if (isExplicitReference) {
              final String columnName = column.getName();
              logicalColumnName =
                  mappings.getLogicalColumnName(columnName, referencedPersistentClass.getTable());
              // JPA 2 requires referencedColumnNames to be case insensitive
              joinColumn = columnByReferencedName.get(logicalColumnName.toLowerCase());
            } else {
              joinColumn = columnByReferencedName.get("" + index);
              index++;
            }
            if (joinColumn == null && !joinColumns[0].isNameDeferred()) {
              throw new AnnotationException(
                  isExplicitReference
                      ? "Unable to find column reference in the @MapsId mapping: "
                          + logicalColumnName
                      : "Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: "
                          + referencedEntityName);
            }
            final String columnName =
                joinColumn == null || joinColumn.isNameDeferred()
                    ? "tata_" + column.getName()
                    : joinColumn.getName();
            value.addColumn(new Column(columnName));
            column.setValue(value);
          }
        }
        component.addProperty(property);
      }
    }
  }
  public static int checkReferencedColumnsType(
      Ejb3JoinColumn[] columns, PersistentClass referencedEntity, MetadataBuildingContext context) {
    // convenient container to find whether a column is an id one or not
    Set<Column> idColumns = new HashSet<Column>();
    Iterator idColumnsIt = referencedEntity.getKey().getColumnIterator();
    while (idColumnsIt.hasNext()) {
      idColumns.add((Column) idColumnsIt.next());
    }

    boolean isFkReferencedColumnName = false;
    boolean noReferencedColumn = true;
    // build the list of potential tables
    if (columns.length == 0) return NO_REFERENCE; // shortcut
    Object columnOwner =
        BinderHelper.findColumnOwner(referencedEntity, columns[0].getReferencedColumn(), context);
    if (columnOwner == null) {
      try {
        throw new MappingException(
            "Unable to find column with logical name: "
                + columns[0].getReferencedColumn()
                + " in "
                + referencedEntity.getTable()
                + " and its related "
                + "supertables and secondary tables");
      } catch (MappingException e) {
        throw new RecoverableException(e.getMessage(), e);
      }
    }
    Table matchingTable =
        columnOwner instanceof PersistentClass
            ? ((PersistentClass) columnOwner).getTable()
            : ((Join) columnOwner).getTable();
    // check each referenced column
    for (Ejb3JoinColumn ejb3Column : columns) {
      String logicalReferencedColumnName = ejb3Column.getReferencedColumn();
      if (StringHelper.isNotEmpty(logicalReferencedColumnName)) {
        String referencedColumnName;
        try {
          referencedColumnName =
              context
                  .getMetadataCollector()
                  .getPhysicalColumnName(matchingTable, logicalReferencedColumnName);
        } catch (MappingException me) {
          // rewrite the exception
          throw new MappingException(
              "Unable to find column with logical name: "
                  + logicalReferencedColumnName
                  + " in "
                  + matchingTable.getName());
        }
        noReferencedColumn = false;
        Column refCol = new Column(referencedColumnName);
        boolean contains = idColumns.contains(refCol);
        if (!contains) {
          isFkReferencedColumnName = true;
          break; // we know the state
        }
      }
    }
    if (isFkReferencedColumnName) {
      return NON_PK_REFERENCE;
    } else if (noReferencedColumn) {
      return NO_REFERENCE;
    } else if (idColumns.size() != columns.length) {
      // reference use PK but is a subset or a superset
      return NON_PK_REFERENCE;
    } else {
      return PK_REFERENCE;
    }
  }
  // TODO refactor this code, there is a lot of duplication in this method
  public void doSecondPass(Map persistentClasses) throws MappingException {
    org.hibernate.mapping.OneToOne value =
        new org.hibernate.mapping.OneToOne(
            mappings, propertyHolder.getTable(), propertyHolder.getPersistentClass());
    final String propertyName = inferredData.getPropertyName();
    value.setPropertyName(propertyName);
    String referencedEntityName =
        ToOneBinder.getReferenceEntityName(inferredData, targetEntity, mappings);
    value.setReferencedEntityName(referencedEntityName);
    AnnotationBinder.defineFetchingStrategy(value, inferredData.getProperty());
    // value.setFetchMode( fetchMode );
    value.setCascadeDeleteEnabled(cascadeOnDelete);
    // value.setLazy( fetchMode != FetchMode.JOIN );

    if (!optional) value.setConstrained(true);
    value.setForeignKeyType(
        value.isConstrained()
            ? ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT
            : ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
    PropertyBinder binder = new PropertyBinder();
    binder.setName(propertyName);
    binder.setValue(value);
    binder.setCascade(cascadeStrategy);
    binder.setAccessType(inferredData.getDefaultAccess());
    Property prop = binder.makeProperty();
    if (BinderHelper.isEmptyAnnotationValue(mappedBy)) {
      /*
       * we need to check if the columns are in the right order
       * if not, then we need to create a many to one and formula
       * but actually, since entities linked by a one to one need
       * to share the same composite id class, this cannot happen in hibernate
       */
      boolean rightOrder = true;

      if (rightOrder) {
        String path = StringHelper.qualify(propertyHolder.getPath(), propertyName);
        (new ToOneFkSecondPass(
                value,
                joinColumns,
                !optional, // cannot have nullabe and unique on certain DBs
                propertyHolder.getEntityOwnerClassName(),
                path,
                mappings))
            .doSecondPass(persistentClasses);
        // no column associated since its a one to one
        propertyHolder.addProperty(prop, inferredData.getDeclaringClass());
      } else {
        // this is a many to one with Formula

      }
    } else {
      PersistentClass otherSide =
          (PersistentClass) persistentClasses.get(value.getReferencedEntityName());
      Property otherSideProperty;
      try {
        if (otherSide == null) {
          throw new MappingException("Unable to find entity: " + value.getReferencedEntityName());
        }
        otherSideProperty = BinderHelper.findPropertyByName(otherSide, mappedBy);
      } catch (MappingException e) {
        throw new AnnotationException(
            "Unknown mappedBy in: "
                + StringHelper.qualify(ownerEntity, ownerProperty)
                + ", referenced property unknown: "
                + StringHelper.qualify(value.getReferencedEntityName(), mappedBy));
      }
      if (otherSideProperty == null) {
        throw new AnnotationException(
            "Unknown mappedBy in: "
                + StringHelper.qualify(ownerEntity, ownerProperty)
                + ", referenced property unknown: "
                + StringHelper.qualify(value.getReferencedEntityName(), mappedBy));
      }
      if (otherSideProperty.getValue() instanceof OneToOne) {
        propertyHolder.addProperty(prop, inferredData.getDeclaringClass());
      } else if (otherSideProperty.getValue() instanceof ManyToOne) {
        Iterator it = otherSide.getJoinIterator();
        Join otherSideJoin = null;
        while (it.hasNext()) {
          Join otherSideJoinValue = (Join) it.next();
          if (otherSideJoinValue.containsProperty(otherSideProperty)) {
            otherSideJoin = otherSideJoinValue;
            break;
          }
        }
        if (otherSideJoin != null) {
          // @OneToOne @JoinTable
          Join mappedByJoin =
              buildJoinFromMappedBySide(
                  (PersistentClass) persistentClasses.get(ownerEntity),
                  otherSideProperty,
                  otherSideJoin);
          ManyToOne manyToOne = new ManyToOne(mappings, mappedByJoin.getTable());
          // FIXME use ignore not found here
          manyToOne.setIgnoreNotFound(ignoreNotFound);
          manyToOne.setCascadeDeleteEnabled(value.isCascadeDeleteEnabled());
          manyToOne.setEmbedded(value.isEmbedded());
          manyToOne.setFetchMode(value.getFetchMode());
          manyToOne.setLazy(value.isLazy());
          manyToOne.setReferencedEntityName(value.getReferencedEntityName());
          manyToOne.setUnwrapProxy(value.isUnwrapProxy());
          prop.setValue(manyToOne);
          Iterator otherSideJoinKeyColumns = otherSideJoin.getKey().getColumnIterator();
          while (otherSideJoinKeyColumns.hasNext()) {
            Column column = (Column) otherSideJoinKeyColumns.next();
            Column copy = new Column();
            copy.setLength(column.getLength());
            copy.setScale(column.getScale());
            copy.setValue(manyToOne);
            copy.setName(column.getQuotedName());
            copy.setNullable(column.isNullable());
            copy.setPrecision(column.getPrecision());
            copy.setUnique(column.isUnique());
            copy.setSqlType(column.getSqlType());
            copy.setCheckConstraint(column.getCheckConstraint());
            copy.setComment(column.getComment());
            copy.setDefaultValue(column.getDefaultValue());
            manyToOne.addColumn(copy);
          }
          mappedByJoin.addProperty(prop);
        } else {
          propertyHolder.addProperty(prop, inferredData.getDeclaringClass());
        }

        value.setReferencedPropertyName(mappedBy);

        String propertyRef = value.getReferencedPropertyName();
        if (propertyRef != null) {
          mappings.addUniquePropertyReference(value.getReferencedEntityName(), propertyRef);
        }
      } else {
        throw new AnnotationException(
            "Referenced property not a (One|Many)ToOne: "
                + StringHelper.qualify(otherSide.getEntityName(), mappedBy)
                + " in mappedBy of "
                + StringHelper.qualify(ownerEntity, ownerProperty));
      }
    }
    ForeignKey fk = inferredData.getProperty().getAnnotation(ForeignKey.class);
    String fkName = fk != null ? fk.name() : "";
    if (!BinderHelper.isEmptyAnnotationValue(fkName)) value.setForeignKeyName(fkName);
  }