package org.exolab.castor.builder.factory; import org.castor.xml.JavaNaming; import org.exolab.castor.builder.AnnotationBuilder; import org.exolab.castor.builder.SGTypes; import org.exolab.castor.builder.info.CollectionInfo; import org.exolab.castor.builder.info.FieldInfo; import org.exolab.castor.builder.info.nature.XMLInfoNature; import org.exolab.castor.builder.types.XSType; import org.exolab.javasource.JArrayType; import org.exolab.javasource.JClass; import org.exolab.javasource.JCollectionType; import org.exolab.javasource.JDocComment; import org.exolab.javasource.JDocDescriptor; import org.exolab.javasource.JMethod; import org.exolab.javasource.JParameter; import org.exolab.javasource.JSourceCode; import org.exolab.javasource.JType; /** * This factory takes a CollectionInfo and generates the suitable JFields * and the accessor methods into the JClass. */ public class CollectionMemberAndAccessorFactory extends FieldMemberAndAccessorFactory { /** * Creates a factory that offers public methods to create the * field initialization code as well as the accessor methods. * * @param naming JavaNaming to use */ public CollectionMemberAndAccessorFactory(final JavaNaming naming) { super(naming); } /** * {@inheritDoc} */ public void generateInitializerCode(final FieldInfo fieldInfo, final JSourceCode sourceCode) { CollectionInfo collectionInfo = (CollectionInfo) fieldInfo; sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(" = new "); JType jType = collectionInfo.getXSList().getJType(); sourceCode.append(((JCollectionType) jType).getInstanceName()); sourceCode.append("();"); } // -- generateConstructorCode /** * {@inheritDoc} */ public final void createAccessMethods(final FieldInfo fieldInfo, final JClass jClass, final boolean useJava50, final AnnotationBuilder[] annotationBuilders) { CollectionInfo collectionInfo = (CollectionInfo) fieldInfo; this.createAddAndRemoveMethods(collectionInfo, jClass); this.createGetAndSetMethods(collectionInfo, jClass, useJava50, annotationBuilders); this.createGetCountMethod(collectionInfo, jClass); this.createCollectionIterationMethods(collectionInfo, jClass, useJava50); } // -- createAccessMethods /** * Creates the add method for this collection. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ protected void createAddMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod(fieldInfo.getWriteMethodName()); method.addException(SGTypes.INDEX_OUT_OF_BOUNDS_EXCEPTION, "if the index given is outside the bounds of the collection"); final JParameter parameter = new JParameter( fieldInfo.getContentType().getJType(), fieldInfo.getContentName()); method.addParameter(parameter); JSourceCode sourceCode = method.getSourceCode(); this.addMaxSizeCheck(fieldInfo, method.getName(), sourceCode); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".addElement("); sourceCode.append(fieldInfo.getContentType().createToJavaObjectCode(parameter.getName())); sourceCode.append(");"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates bound property code.. * * @param fieldInfo the collectionInfo to translate * @param sourceCode the sourceCode to attach to */ protected void createBoundPropertyCode(final CollectionInfo fieldInfo, final JSourceCode sourceCode) { sourceCode.add("notifyPropertyChangeListeners(\""); String fieldName = fieldInfo.getName(); if (fieldName.startsWith("_")) { sourceCode.append(fieldName.substring(1)); } else { sourceCode.append(fieldName); } sourceCode.append("\", null, "); sourceCode.append(fieldName); sourceCode.append(");"); } // -- createBoundPropertyCode /** * Creates the enumerate method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag */ protected void createEnumerateMethod(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50) { JMethod method = new JMethod("enumerate" + fieldInfo.getMethodSuffix(), SGTypes.createEnumeration(fieldInfo.getContentType().getJType(), useJava50, true), "an Enumeration over all " + fieldInfo.getContentType().getJType() + " elements"); JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("return this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".elements();"); jClass.addMethod(method); } /** * Returns true if extra collection methods should be generated. The extra * collection methods are methods which return an actual reference to the * underlying collection as opposed to an enumeration, iterator, or copy. * * @param fieldInfo the collectionInfo to translate * @return true if extra collection methods should be generated */ private boolean createExtraMethods(final CollectionInfo fieldInfo) { return fieldInfo.isExtraMethods(); } // -- extraMethods /** * Creates the get as array method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag * @param annotationBuilders the custom builders */ private void createGetAsArrayMethod(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50, AnnotationBuilder[] annotationBuilders) { JType baseType = fieldInfo.getContentType().getJType(); JType arrayType = new JArrayType(baseType, useJava50); JMethod method = new JMethod(fieldInfo.getReadMethodName(), arrayType, "this collection as an Array"); JSourceCode sourceCode = method.getSourceCode(); // create Javadoc JDocComment comment = method.getJDocComment(); comment.appendComment("Returns the contents of the collection in an Array. "); if (!(baseType.isPrimitive())) { // For non-primitive types, we use the API method made for this purpose comment.appendComment("<p>"); comment.appendComment("Note: Just in case the collection contents are changing in "); comment.appendComment("another thread, we pass a 0-length Array of the correct type "); comment.appendComment("into the API call. This way we <i>know</i> that the Array "); comment.appendComment("returned is of exactly the correct length."); String baseTypeName = baseType.toString(); if (baseType.isArray()) { sourceCode.add(arrayType.toString() + " array = new "); sourceCode.append(baseTypeName.substring(0, baseTypeName.length() - 2) + "[0][];"); } else { sourceCode.add(arrayType.toString() + " array = new "); sourceCode.append(baseTypeName + "[0];"); } sourceCode.add("return (" + arrayType.toString() + ") "); sourceCode.append("this." + fieldInfo.getName() + ".toArray(array);"); } else { // For primitive types, we have to do this the hard way sourceCode.add("int size = this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".size();"); sourceCode.add(arrayType.toString()); sourceCode.append(" array = new "); // the first brackets must contain the size... int brackets = arrayType.toString().indexOf("[]"); sourceCode.append(arrayType.toString().substring(0, brackets)); sourceCode.append("[size]"); sourceCode.append(";"); sourceCode.add("java.util.Iterator iter = " + fieldInfo.getName() + ".iterator();"); String value = "iter.next()"; sourceCode.add("for (int index = 0; index < size; index++) {"); sourceCode.indent(); sourceCode.add("array[index] = "); if (fieldInfo.getContentType().getType() == XSType.CLASS) { sourceCode.append("("); sourceCode.append(arrayType.getName()); sourceCode.append(") "); sourceCode.append(value); } else { sourceCode.append(fieldInfo.getContentType().createFromJavaObjectCode(value)); } sourceCode.append(";"); sourceCode.unindent(); sourceCode.add("}"); sourceCode.add("return array;"); } // add custom annotations for (int i = 0; i < annotationBuilders.length; i++) { AnnotationBuilder annotationBuilder = annotationBuilders[i]; annotationBuilder.addFieldGetterAnnotations(fieldInfo, method); } jClass.addMethod(method); } /** * Creates the get as reference method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ private void createGetAsReferenceMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod(fieldInfo.getReadMethodName() + fieldInfo.getReferenceMethodSuffix(), fieldInfo.getXSList().getJType(), "a reference to the Vector backing this class"); // create Javadoc JDocComment comment = method.getJDocComment(); comment.appendComment("Returns a reference to '"); comment.appendComment(fieldInfo.getName()); comment.appendComment("'. No type checking is performed on any "); comment.appendComment("modifications to the Vector."); // create code JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("return this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(";"); jClass.addMethod(method); } /** * Creates the get by index method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ protected void createGetByIndexMethod(final CollectionInfo fieldInfo, final JClass jClass) { XSType contentType = fieldInfo.getContentType(); JMethod method = new JMethod(fieldInfo.getReadMethodName(), contentType.getJType(), "the value of the " + contentType.getJType().toString() + " at the given index"); method.addException(SGTypes.INDEX_OUT_OF_BOUNDS_EXCEPTION, "if the index given is outside the bounds of the collection"); method.addParameter(new JParameter(JType.INT, "index")); JSourceCode sourceCode = method.getSourceCode(); this.addIndexCheck(fieldInfo, sourceCode, method.getName()); String value = fieldInfo.getName() + ".get(index)"; sourceCode.add("return "); if (contentType.getType() == XSType.CLASS) { sourceCode.append("("); sourceCode.append(method.getReturnType().toString()); sourceCode.append(") "); sourceCode.append(value); } else { sourceCode.append(contentType.createFromJavaObjectCode(value)); } sourceCode.append(";"); jClass.addMethod(method); } /** * Creates the add/add by index/remove/remove by index/remove all methods. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ private void createAddAndRemoveMethods(final CollectionInfo fieldInfo, final JClass jClass) { // create add methods this.createAddMethod(fieldInfo, jClass); this.createAddByIndexMethod(fieldInfo, jClass); // create remove methods this.createRemoveObjectMethod(fieldInfo, jClass); this.createRemoveByIndexMethod(fieldInfo, jClass); this.createRemoveAllMethod(fieldInfo,jClass); } /** * Creates the getter and setter methods. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag * @param annotationBuilders the custom annotationBuilders */ private void createGetAndSetMethods(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50, final AnnotationBuilder[] annotationBuilders) { // create get methods this.createGetByIndexMethod(fieldInfo, jClass); this.createGetAsArrayMethod(fieldInfo, jClass, useJava50, annotationBuilders); if (this.createExtraMethods(fieldInfo)) { this.createGetAsReferenceMethod(fieldInfo, jClass); } // create set methods this.createSetByIndexMethod(fieldInfo, jClass); this.createSetAsArrayMethod(fieldInfo, jClass, useJava50); if (this.createExtraMethods(fieldInfo)) { this.createSetAsCopyMethod(fieldInfo, jClass); this.createSetAsReferenceMethod(fieldInfo, jClass, useJava50); } } /** * Creates the get count method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ private void createGetCountMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod(fieldInfo.getReadMethodName() + "Count", JType.INT, "the size of this collection"); JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("return this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".size();"); jClass.addMethod(method); } /** * Generate methods for iterating over the objects in the collection. For * Java-1 collections, we only generate an Enumerator. Implementations for * other versions of Java should call this method for backward compatbility * and then add any additional new methods. * * @param fieldInfo the collectionI to translate * @param jClass the JClass to which we add this method * @param useJava50 * true if source code is supposed to be generated for Java 5 */ protected void createCollectionIterationMethods(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50) { this.createEnumerateMethod(fieldInfo, jClass, useJava50); } // -- createCollectionAccessMethods /** * Creates the add by index method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag */ protected void createAddByIndexMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod(fieldInfo.getWriteMethodName()); method.addException(SGTypes.INDEX_OUT_OF_BOUNDS_EXCEPTION, "if the index given is outside the bounds of the collection"); method.addParameter(new JParameter(JType.INT, "index")); final JParameter parameter = new JParameter( fieldInfo.getContentType().getJType(), fieldInfo.getContentName()); method.addParameter(parameter); JSourceCode sourceCode = method.getSourceCode(); this.addMaxSizeCheck(fieldInfo, method.getName(), sourceCode); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".add(index, "); sourceCode.append(fieldInfo.getContentType().createToJavaObjectCode(parameter.getName())); sourceCode.append(");"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates the iterate method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag */ protected void createIteratorMethod(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50) { JMethod method = new JMethod("iterate" + fieldInfo.getMethodSuffix(), SGTypes.createIterator(fieldInfo.getContentType().getJType(), useJava50, true), "an Iterator over all possible elements in this collection"); JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("return this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".iterator();"); jClass.addMethod(method); } /** * Creates the remove all method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ private void createRemoveAllMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod("removeAll" + fieldInfo.getMethodSuffix()); JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".clear();"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates the remove by index method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ protected void createRemoveByIndexMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod("remove" + fieldInfo.getMethodSuffix() + "At", fieldInfo.getContentType().getJType(), "the element removed from the collection"); method.addParameter(new JParameter(JType.INT, "index")); JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("java.lang.Object obj = this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".remove(index);"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } sourceCode.add("return "); if (fieldInfo.getContentType().getType() == XSType.CLASS) { sourceCode.append("("); sourceCode.append(method.getReturnType().getName()); sourceCode.append(") obj;"); } else { sourceCode.append(fieldInfo.getContentType().createFromJavaObjectCode("obj")); sourceCode.append(";"); } jClass.addMethod(method); } /** * Creates the remove by object method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ private void createRemoveObjectMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod("remove" + fieldInfo.getMethodSuffix(), JType.BOOLEAN, "true if the object was removed from the collection."); final JParameter parameter = new JParameter(fieldInfo.getContentType().getJType(), fieldInfo.getContentName()); method.addParameter(parameter); JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("boolean removed = "); sourceCode.append(fieldInfo.getName()); sourceCode.append(".remove("); sourceCode.append(fieldInfo.getContentType().createToJavaObjectCode(parameter.getName())); sourceCode.append(");"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } sourceCode.add("return removed;"); jClass.addMethod(method); } /** * Creates the set as array method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag */ private void createSetAsArrayMethod(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50) { JMethod method = new JMethod("set" + fieldInfo.getMethodSuffix()); final JParameter parameter = new JParameter(new JArrayType( fieldInfo.getContentType().getJType(), useJava50), fieldInfo.getContentName() + "Array"); method.addParameter(parameter); JSourceCode sourceCode = method.getSourceCode(); String index = "i"; if (parameter.getName().equals(index)) { index = "j"; } sourceCode.add("//-- copy array"); sourceCode.add(fieldInfo.getName()); sourceCode.append(".clear();"); sourceCode.add(""); sourceCode.add("for (int "); sourceCode.append(index); sourceCode.append(" = 0; "); sourceCode.append(index); sourceCode.append(" < "); sourceCode.append(parameter.getName()); sourceCode.append(".length; "); sourceCode.append(index); sourceCode.append("++) {"); sourceCode.indent(); sourceCode.addIndented("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".add("); sourceCode.append(fieldInfo.getContentType().createToJavaObjectCode( parameter.getName() + "[" + index + "]")); sourceCode.append(");"); sourceCode.unindent(); sourceCode.add("}"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates the set as copy method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ private void createSetAsCopyMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod("set" + fieldInfo.getMethodSuffix()); JParameter parameter = new JParameter(fieldInfo.getXSList().getJType(), fieldInfo.getContentName() + "List"); method.addParameter(parameter); // create Javadoc JDocComment comment = method.getJDocComment(); comment.appendComment("Sets the value of '"); comment.appendComment(fieldInfo.getName()); comment.appendComment( "' by copying the given Vector. All elements will be checked for type safety."); JDocDescriptor jDesc = comment.getParamDescriptor(parameter.getName()); jDesc.setDescription("the Vector to copy."); // create code JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("// copy vector"); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".clear();"); sourceCode.add(""); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".addAll("); sourceCode.append(parameter.getName()); sourceCode.append(");"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates the set as reference method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. * @param useJava50 java version flag */ private void createSetAsReferenceMethod(final CollectionInfo fieldInfo, final JClass jClass, final boolean useJava50) { JMethod method = new JMethod("set" + fieldInfo.getMethodSuffix() + fieldInfo.getReferenceSuffix()); final JType collectionJType = new XMLInfoNature(fieldInfo).getSchemaType().getJType(); JParameter parameter = new JParameter( collectionJType, fieldInfo.getParameterPrefix() + collectionJType.getLocalName()); method.addParameter(parameter); // create Javadoc JDocComment comment = method.getJDocComment(); comment.appendComment("Sets the value of '"); comment.appendComment(fieldInfo.getName()); comment.appendComment("' by setting it to the given Vector."); comment.appendComment(" No type checking is performed."); comment.appendComment("\n@deprecated"); JDocDescriptor jDesc = comment.getParamDescriptor(parameter.getName()); jDesc.setDescription("the Vector to set."); // create code JSourceCode sourceCode = method.getSourceCode(); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(" = "); sourceCode.append(parameter.getName()); sourceCode.append(";"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates the set by index method. * * @param fieldInfo the collectionInfo to translate * @param jClass the jClass to add the method to. */ protected void createSetByIndexMethod(final CollectionInfo fieldInfo, final JClass jClass) { JMethod method = new JMethod("set" + fieldInfo.getMethodSuffix()); method.addException(SGTypes.INDEX_OUT_OF_BOUNDS_EXCEPTION, "if the index given is outside the bounds of the collection"); method.addParameter(new JParameter(JType.INT, "index")); method.addParameter(new JParameter(fieldInfo.getContentType().getJType(), fieldInfo.getContentName())); JSourceCode sourceCode = method.getSourceCode(); this.addIndexCheck(fieldInfo, sourceCode, method.getName()); sourceCode.add("this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".set(index, "); sourceCode.append(fieldInfo.getContentType().createToJavaObjectCode( fieldInfo.getContentName())); sourceCode.append(");"); if (fieldInfo.isBound()) { this.createBoundPropertyCode(fieldInfo, sourceCode); } jClass.addMethod(method); } /** * Creates the add max size check. * * @param fieldInfo the collectionInfo to translate * @param methodName the method name * @param sourceCode the sourceCode to attach to */ protected void addMaxSizeCheck(final CollectionInfo fieldInfo, final String methodName, final JSourceCode sourceCode) { if (fieldInfo.getXSList().getMaximumSize() > 0) { final String size = Integer.toString(fieldInfo.getXSList().getMaximumSize()); sourceCode.add("// check for the maximum size"); sourceCode.add("if (this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".size() >= "); sourceCode.append(size); sourceCode.append(") {"); sourceCode.indent(); sourceCode.add("throw new IndexOutOfBoundsException(\""); sourceCode.append(methodName); sourceCode.append(" has a maximum of "); sourceCode.append(size); sourceCode.append("\");"); sourceCode.unindent(); sourceCode.add("}"); sourceCode.add(""); } } /** * Creates the index check. * * @param fieldInfo the collectionInfo to translate * @param methodName the method name * @param sourceCode the sourceCode to attach to */ private void addIndexCheck(final CollectionInfo fieldInfo, final JSourceCode sourceCode, final String methodName) { sourceCode.add("// check bounds for index"); sourceCode.add("if (index < 0 || index >= this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".size()) {"); sourceCode.indent(); sourceCode.add("throw new IndexOutOfBoundsException(\""); sourceCode.append(methodName); sourceCode.append(": Index value '\" + index + \"' not in range [0..\" + (this."); sourceCode.append(fieldInfo.getName()); sourceCode.append(".size() - 1) + \"]\");"); sourceCode.unindent(); sourceCode.add("}"); sourceCode.add(""); } }