/**
* Copyright 2006-2017 the original author or authors.
*
* Licensed 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
*
* http://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.mybatis.generator.api.dom.java;
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
import static org.mybatis.generator.internal.util.messages.Messages.getString;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
/**
* The Class FullyQualifiedJavaType.
*
* @author Jeff Butler
*/
public class FullyQualifiedJavaType implements
Comparable<FullyQualifiedJavaType> {
/** The Constant JAVA_LANG. */
private static final String JAVA_LANG = "java.lang"; //$NON-NLS-1$
/** The int instance. */
private static FullyQualifiedJavaType intInstance = null;
/** The string instance. */
private static FullyQualifiedJavaType stringInstance = null;
/** The boolean primitive instance. */
private static FullyQualifiedJavaType booleanPrimitiveInstance = null;
/** The object instance. */
private static FullyQualifiedJavaType objectInstance = null;
/** The date instance. */
private static FullyQualifiedJavaType dateInstance = null;
/** The criteria instance. */
private static FullyQualifiedJavaType criteriaInstance = null;
/** The generated criteria instance. */
private static FullyQualifiedJavaType generatedCriteriaInstance = null;
/** The short name without any generic arguments. */
private String baseShortName;
/** The fully qualified name without any generic arguments. */
private String baseQualifiedName;
/** The explicitly imported. */
private boolean explicitlyImported;
/** The package name. */
private String packageName;
/** The primitive. */
private boolean primitive;
/** The is array. */
private boolean isArray;
/** The primitive type wrapper. */
private PrimitiveTypeWrapper primitiveTypeWrapper;
/** The type arguments. */
private List<FullyQualifiedJavaType> typeArguments;
// the following three values are used for dealing with wildcard types
/** The wildcard type. */
private boolean wildcardType;
/** The bounded wildcard. */
private boolean boundedWildcard;
/** The extends bounded wildcard. */
private boolean extendsBoundedWildcard;
/**
* Use this constructor to construct a generic type with the specified type parameters.
*
* @param fullTypeSpecification
* the full type specification
*/
public FullyQualifiedJavaType(String fullTypeSpecification) {
super();
typeArguments = new ArrayList<FullyQualifiedJavaType>();
parse(fullTypeSpecification);
}
/**
* Checks if is explicitly imported.
*
* @return Returns the explicitlyImported.
*/
public boolean isExplicitlyImported() {
return explicitlyImported;
}
/**
* This method returns the fully qualified name - including any generic type parameters.
*
* @return Returns the fullyQualifiedName.
*/
public String getFullyQualifiedName() {
StringBuilder sb = new StringBuilder();
if (wildcardType) {
sb.append('?');
if (boundedWildcard) {
if (extendsBoundedWildcard) {
sb.append(" extends "); //$NON-NLS-1$
} else {
sb.append(" super "); //$NON-NLS-1$
}
sb.append(baseQualifiedName);
}
} else {
sb.append(baseQualifiedName);
}
if (typeArguments.size() > 0) {
boolean first = true;
sb.append('<');
for (FullyQualifiedJavaType fqjt : typeArguments) {
if (first) {
first = false;
} else {
sb.append(", "); //$NON-NLS-1$
}
sb.append(fqjt.getFullyQualifiedName());
}
sb.append('>');
}
return sb.toString();
}
public String getFullyQualifiedNameWithoutTypeParameters() {
return baseQualifiedName;
}
/**
* Returns a list of Strings that are the fully qualified names of this type, and any generic type argument
* associated with this type.
*
* @return the import list
*/
public List<String> getImportList() {
List<String> answer = new ArrayList<String>();
if (isExplicitlyImported()) {
int index = baseShortName.indexOf('.');
if (index == -1) {
answer.add(calculateActualImport(baseQualifiedName));
} else {
// an inner class is specified, only import the top
// level class
StringBuilder sb = new StringBuilder();
sb.append(packageName);
sb.append('.');
sb.append(calculateActualImport(baseShortName.substring(0, index)));
answer.add(sb.toString());
}
}
for (FullyQualifiedJavaType fqjt : typeArguments) {
answer.addAll(fqjt.getImportList());
}
return answer;
}
private String calculateActualImport(String name) {
String answer = name;
if (this.isArray()) {
int index = name.indexOf("[");
if (index != -1) {
answer = name.substring(0, index);
}
}
return answer;
}
/**
* Gets the package name.
*
* @return Returns the packageName.
*/
public String getPackageName() {
return packageName;
}
/**
* Gets the short name.
*
* @return Returns the shortName - including any type arguments.
*/
public String getShortName() {
StringBuilder sb = new StringBuilder();
if (wildcardType) {
sb.append('?');
if (boundedWildcard) {
if (extendsBoundedWildcard) {
sb.append(" extends "); //$NON-NLS-1$
} else {
sb.append(" super "); //$NON-NLS-1$
}
sb.append(baseShortName);
}
} else {
sb.append(baseShortName);
}
if (typeArguments.size() > 0) {
boolean first = true;
sb.append('<');
for (FullyQualifiedJavaType fqjt : typeArguments) {
if (first) {
first = false;
} else {
sb.append(", "); //$NON-NLS-1$
}
sb.append(fqjt.getShortName());
}
sb.append('>');
}
return sb.toString();
}
public String getShortNameWithoutTypeArguments() {
return baseShortName;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FullyQualifiedJavaType)) {
return false;
}
FullyQualifiedJavaType other = (FullyQualifiedJavaType) obj;
return getFullyQualifiedName().equals(other.getFullyQualifiedName());
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return getFullyQualifiedName().hashCode();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getFullyQualifiedName();
}
/**
* Checks if is primitive.
*
* @return Returns the primitive.
*/
public boolean isPrimitive() {
return primitive;
}
/**
* Gets the primitive type wrapper.
*
* @return Returns the wrapperClass.
*/
public PrimitiveTypeWrapper getPrimitiveTypeWrapper() {
return primitiveTypeWrapper;
}
/**
* Gets the int instance.
*
* @return the int instance
*/
public static final FullyQualifiedJavaType getIntInstance() {
if (intInstance == null) {
intInstance = new FullyQualifiedJavaType("int"); //$NON-NLS-1$
}
return intInstance;
}
/**
* Gets the new map instance.
*
* @return the new map instance
*/
public static final FullyQualifiedJavaType getNewMapInstance() {
// always return a new instance because the type may be parameterized
return new FullyQualifiedJavaType("java.util.Map"); //$NON-NLS-1$
}
/**
* Gets the new list instance.
*
* @return the new list instance
*/
public static final FullyQualifiedJavaType getNewListInstance() {
// always return a new instance because the type may be parameterized
return new FullyQualifiedJavaType("java.util.List"); //$NON-NLS-1$
}
/**
* Gets the new hash map instance.
*
* @return the new hash map instance
*/
public static final FullyQualifiedJavaType getNewHashMapInstance() {
// always return a new instance because the type may be parameterized
return new FullyQualifiedJavaType("java.util.HashMap"); //$NON-NLS-1$
}
/**
* Gets the new array list instance.
*
* @return the new array list instance
*/
public static final FullyQualifiedJavaType getNewArrayListInstance() {
// always return a new instance because the type may be parameterized
return new FullyQualifiedJavaType("java.util.ArrayList"); //$NON-NLS-1$
}
/**
* Gets the new iterator instance.
*
* @return the new iterator instance
*/
public static final FullyQualifiedJavaType getNewIteratorInstance() {
// always return a new instance because the type may be parameterized
return new FullyQualifiedJavaType("java.util.Iterator"); //$NON-NLS-1$
}
/**
* Gets the string instance.
*
* @return the string instance
*/
public static final FullyQualifiedJavaType getStringInstance() {
if (stringInstance == null) {
stringInstance = new FullyQualifiedJavaType("java.lang.String"); //$NON-NLS-1$
}
return stringInstance;
}
/**
* Gets the boolean primitive instance.
*
* @return the boolean primitive instance
*/
public static final FullyQualifiedJavaType getBooleanPrimitiveInstance() {
if (booleanPrimitiveInstance == null) {
booleanPrimitiveInstance = new FullyQualifiedJavaType("boolean"); //$NON-NLS-1$
}
return booleanPrimitiveInstance;
}
/**
* Gets the object instance.
*
* @return the object instance
*/
public static final FullyQualifiedJavaType getObjectInstance() {
if (objectInstance == null) {
objectInstance = new FullyQualifiedJavaType("java.lang.Object"); //$NON-NLS-1$
}
return objectInstance;
}
/**
* Gets the date instance.
*
* @return the date instance
*/
public static final FullyQualifiedJavaType getDateInstance() {
if (dateInstance == null) {
dateInstance = new FullyQualifiedJavaType("java.util.Date"); //$NON-NLS-1$
}
return dateInstance;
}
/**
* Gets the criteria instance.
*
* @return the criteria instance
*/
public static final FullyQualifiedJavaType getCriteriaInstance() {
if (criteriaInstance == null) {
criteriaInstance = new FullyQualifiedJavaType("Criteria"); //$NON-NLS-1$
}
return criteriaInstance;
}
/**
* Gets the generated criteria instance.
*
* @return the generated criteria instance
*/
public static final FullyQualifiedJavaType getGeneratedCriteriaInstance() {
if (generatedCriteriaInstance == null) {
generatedCriteriaInstance = new FullyQualifiedJavaType(
"GeneratedCriteria"); //$NON-NLS-1$
}
return generatedCriteriaInstance;
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(FullyQualifiedJavaType other) {
return getFullyQualifiedName().compareTo(other.getFullyQualifiedName());
}
/**
* Adds the type argument.
*
* @param type
* the type
*/
public void addTypeArgument(FullyQualifiedJavaType type) {
typeArguments.add(type);
}
/**
* Parses the.
*
* @param fullTypeSpecification
* the full type specification
*/
private void parse(String fullTypeSpecification) {
String spec = fullTypeSpecification.trim();
if (spec.startsWith("?")) { //$NON-NLS-1$
wildcardType = true;
spec = spec.substring(1).trim();
if (spec.startsWith("extends ")) { //$NON-NLS-1$
boundedWildcard = true;
extendsBoundedWildcard = true;
spec = spec.substring(8); // "extends ".length()
} else if (spec.startsWith("super ")) { //$NON-NLS-1$
boundedWildcard = true;
extendsBoundedWildcard = false;
spec = spec.substring(6); // "super ".length()
} else {
boundedWildcard = false;
}
parse(spec);
} else {
int index = fullTypeSpecification.indexOf('<');
if (index == -1) {
simpleParse(fullTypeSpecification);
} else {
simpleParse(fullTypeSpecification.substring(0, index));
int endIndex = fullTypeSpecification.lastIndexOf('>');
if (endIndex == -1) {
throw new RuntimeException(getString(
"RuntimeError.22", fullTypeSpecification)); //$NON-NLS-1$
}
genericParse(fullTypeSpecification.substring(index, endIndex + 1));
}
// this is far from a perfect test for detecting arrays, but is close
// enough for most cases. It will not detect an improperly specified
// array type like byte], but it will detect byte[] and byte[ ]
// which are both valid
isArray = fullTypeSpecification.endsWith("]"); //$NON-NLS-1$
}
}
/**
* Simple parse.
*
* @param typeSpecification
* the type specification
*/
private void simpleParse(String typeSpecification) {
baseQualifiedName = typeSpecification.trim();
if (baseQualifiedName.contains(".")) { //$NON-NLS-1$
packageName = getPackage(baseQualifiedName);
baseShortName = baseQualifiedName
.substring(packageName.length() + 1);
int index = baseShortName.lastIndexOf('.');
if (index != -1) {
baseShortName = baseShortName.substring(index + 1);
}
if (JAVA_LANG.equals(packageName)) { //$NON-NLS-1$
explicitlyImported = false;
} else {
explicitlyImported = true;
}
} else {
baseShortName = baseQualifiedName;
explicitlyImported = false;
packageName = ""; //$NON-NLS-1$
if ("byte".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper.getByteInstance();
} else if ("short".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper.getShortInstance();
} else if ("int".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper
.getIntegerInstance();
} else if ("long".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper.getLongInstance();
} else if ("char".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper
.getCharacterInstance();
} else if ("float".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper.getFloatInstance();
} else if ("double".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper.getDoubleInstance();
} else if ("boolean".equals(baseQualifiedName)) { //$NON-NLS-1$
primitive = true;
primitiveTypeWrapper = PrimitiveTypeWrapper
.getBooleanInstance();
} else {
primitive = false;
primitiveTypeWrapper = null;
}
}
}
/**
* Generic parse.
*
* @param genericSpecification
* the generic specification
*/
private void genericParse(String genericSpecification) {
int lastIndex = genericSpecification.lastIndexOf('>');
if (lastIndex == -1) {
// shouldn't happen - should be caught already, but just in case...
throw new RuntimeException(getString(
"RuntimeError.22", genericSpecification)); //$NON-NLS-1$
}
String argumentString = genericSpecification.substring(1, lastIndex);
// need to find "," outside of a <> bounds
StringTokenizer st = new StringTokenizer(argumentString, ",<>", true); //$NON-NLS-1$
int openCount = 0;
StringBuilder sb = new StringBuilder();
while (st.hasMoreTokens()) {
String token = st.nextToken();
if ("<".equals(token)) { //$NON-NLS-1$
sb.append(token);
openCount++;
} else if (">".equals(token)) { //$NON-NLS-1$
sb.append(token);
openCount--;
} else if (",".equals(token)) { //$NON-NLS-1$
if (openCount == 0) {
typeArguments
.add(new FullyQualifiedJavaType(sb.toString()));
sb.setLength(0);
} else {
sb.append(token);
}
} else {
sb.append(token);
}
}
if (openCount != 0) {
throw new RuntimeException(getString(
"RuntimeError.22", genericSpecification)); //$NON-NLS-1$
}
String finalType = sb.toString();
if (stringHasValue(finalType)) {
typeArguments.add(new FullyQualifiedJavaType(finalType));
}
}
/**
* Returns the package name of a fully qualified type.
*
* This method calculates the package as the part of the fully qualified name up to, but not including, the last
* element. Therefore, it does not support fully qualified inner classes. Not totally fool proof, but correct in
* most instances.
*
* @param baseQualifiedName
* the base qualified name
* @return the package
*/
private static String getPackage(String baseQualifiedName) {
int index = baseQualifiedName.lastIndexOf('.');
return baseQualifiedName.substring(0, index);
}
/**
* Checks if is array.
*
* @return true, if is array
*/
public boolean isArray() {
return isArray;
}
public List<FullyQualifiedJavaType> getTypeArguments() {
return typeArguments;
}
}