Java tutorial
/* * Copyright 2006-2008 Web Cohesion * * 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.codehaus.enunciate.modules.objc; import com.sun.mirror.declaration.ClassDeclaration; import com.sun.mirror.type.ClassType; import freemarker.template.*; import net.sf.jelly.apt.decorations.JavaDoc; import net.sf.jelly.apt.freemarker.FreemarkerJavaDoc; import org.apache.commons.digester.RuleSet; import org.codehaus.enunciate.EnunciateException; import org.codehaus.enunciate.apt.EnunciateFreemarkerModel; import org.codehaus.enunciate.config.SchemaInfo; import org.codehaus.enunciate.contract.jaxb.ElementDeclaration; import org.codehaus.enunciate.contract.jaxb.LocalElementDeclaration; import org.codehaus.enunciate.contract.jaxb.RootElementDeclaration; import org.codehaus.enunciate.contract.jaxb.TypeDefinition; import org.codehaus.enunciate.contract.jaxrs.ResourceMethod; import org.codehaus.enunciate.contract.validation.Validator; import org.codehaus.enunciate.main.ClientLibraryArtifact; import org.codehaus.enunciate.main.NamedFileArtifact; import org.codehaus.enunciate.main.ArtifactType; import org.codehaus.enunciate.modules.FacetAware; import org.codehaus.enunciate.modules.FreemarkerDeploymentModule; import org.codehaus.enunciate.modules.objc.config.ObjCRuleSet; import org.codehaus.enunciate.modules.objc.config.PackageIdentifier; import org.codehaus.enunciate.template.freemarker.AccessorOverridesAnotherMethod; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.net.URL; import java.util.*; import java.util.regex.Pattern; /** * <h1>Objective C Module</h1> * * <p>The Objective C module generates Objective C classes and (de)serialization functions that can be used in conjunction with <a href="http://xmlsoft.org/">libxml2</a> * to (de)serialize the REST resources as they are represented as XML data.</p> * * <p>The order of the Objective C deployment module is 0, as it doesn't depend on any artifacts exported by any other module.</p> * * <ul> * <li><a href="#config">configuration</a></li> * </ul> * * <h1><a name="config">Configuration</a></h1> * * <p>The Objective C module is configured with the "obj-c" element under the "modules" element of the enunciate configuration file. It supports the following * attributes:</p> * * <ul> * <li>The "label" attribute is the label for the Objective C API. This is the name by which the file will be identified (producing [label].m and [label].h). * By default the label is the same as the Enunciate project label.</li> * <li>The "forceEnable" attribute is used to force-enable the Objective C module. By default, the Objective C module is enabled only if REST endpoints are found in the project.</li> * <li>The "enumConstantNamePattern" attribute defines the <a href="http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax">format string</a> for * converting an enum constant name to a unique c-style constant name. The arguments passed to the format string are: (1) the project label (2) the namespace id * of the type definition (3) the name of the type definition (4) the NOT decapitalized annotation-specified client name of the type declaration * (5) the decapitalized annotation-specified client name of the type declaration (6) the NOT-decapitalized simple name of the type declaration * (7) the decapitalized simple name of the type declaration (8) the package identifier (9) the annotation-specified client name of the enum contant * (10) the simple name of the enum constant. All tokens will be "scrubbed" by replacing any non-word character with the "_" character. * The default value for this pattern is "%1$S_%2$S_%3$S_%9$S".</li> * <li>The "typeDefinitionNamePattern" attribute defines the <a href="http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax">format string</a> for * converting an type definition name to a unique c-style name. The arguments passed to the format string are: (1) the project label (2) the namespace id * of the type definition (3) the name of the type definition (4) the NOT decapitalized annotation-specified client name of the type declaration * (5) the decapitalized annotation-specified client name of the type declaration (6) the NOT-decapitalized simple name of the type declaration * (7) the decapitalized simple name of the type declaration (8) the package identifier. All tokens will be "scrubbed" by replacing any non-word character * with the "_" character. The default value for this pattern is "%1$S%2$S%4$s".</li> * <li>The "packageIdentifierPattern" attribute defines the <a href="http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax">format string</a> for * creating a unique package identifier for a given package. The arguments passed to the format string will be each subpackage. So, for package "org.codehaus.enunciate.samples.c", * The arguments are (1) org (2) codehaus (3) enunciate (4) samples (5) c. The default package identifier is the package name. The package identifier * is in turn passed as an argument to the "enumConstantNamePattern" and to the "typeDefinitionNamePattern".</li> * <li>The 'translateIdTo' attribute specifies what to use as the name of an accessor when in Java it's named 'id' (which is a keyword in Objective C).</li> * <li>The 'separateCommonCode' attribute tells Enunciate to keep the code that is common to all Enunciate-generated projects separate from the code that is * generated specifically for this project. Default: true.</li> * </ul> * * <p>In addition to the attributes specified above, the Objective C module configuration supports an arbitrary number of "package" child elements, used to * explicitly assign package identifiers to each package. The "package" child element supports a "name" attribute (used to name the package) and an "identifier" attribute.</p> * * <h3>The "facets" element</h3> * * <p>The "facets" element is applicable to the Objective C module to configure which facets are to be included/excluded from the Objective C artifacts. For * more information, see <a href="http://docs.codehaus.org/display/ENUNCIATE/Enunciate+API+Facets">API Facets</a></p> * * @author Ryan Heaton * @docFileName module_obj_c.html */ public class ObjCDeploymentModule extends FreemarkerDeploymentModule implements FacetAware { /** * The pattern to scrub is any non-word character. */ private static final Pattern SCRUB_PATTERN = Pattern.compile("\\W"); private boolean forceEnable = false; private String label = null; private String packageIdentifierPattern = null; private String typeDefinitionNamePattern = "%1$S%2$S%4$s"; private String enumConstantNamePattern = "%1$S_%2$S_%3$S_%9$S"; private final Map<String, String> packageIdentifiers = new HashMap<String, String>(); private String translateIdTo = "identifier"; private boolean separateCommonCode = true; private Set<String> facetIncludes = new TreeSet<String>(); private Set<String> facetExcludes = new TreeSet<String>(); /** * @return "obj-c" */ @Override public String getName() { return "obj-c"; } /** * Scrub a C identifier (removing any illegal characters, etc.). * * @param identifier The identifier. * @return The identifier. */ public static String scrubIdentifier(String identifier) { return identifier == null ? null : SCRUB_PATTERN.matcher(identifier).replaceAll("_"); } @Override public void initModel(EnunciateFreemarkerModel model) { super.initModel(model); if (!isDisabled() && (this.packageIdentifierPattern != null)) { for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { String pckg = typeDefinition.getPackage().getQualifiedName(); if (!this.packageIdentifiers.containsKey(pckg)) { try { this.packageIdentifiers.put(pckg, String.format(this.packageIdentifierPattern, pckg.split("\\.", 9))); } catch (IllegalFormatException e) { warn("Unable to format package %s with format pattern %s (%s)", pckg, this.packageIdentifierPattern, e.getMessage()); } } } } } } @Override public void doFreemarkerGenerate() throws IOException, TemplateException, EnunciateException { File genDir = getGenerateDir(); String label = getLabel() == null ? getEnunciate().getConfig() == null ? "enunciate" : getEnunciate().getConfig().getLabel() : getLabel(); if (!enunciate.isUpToDateWithSources(genDir)) { EnunciateFreemarkerModel model = getModel(); TreeMap<String, String> translations = new TreeMap<String, String>(); translations.put("id", this.translateIdTo); model.put("clientSimpleName", new ClientSimpleNameMethod(translations)); List<TypeDefinition> schemaTypes = new ArrayList<TypeDefinition>(); ExtensionDepthComparator comparator = new ExtensionDepthComparator(); for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { int position = Collections.binarySearch(schemaTypes, typeDefinition, comparator); if (position < 0) { position = -position - 1; } schemaTypes.add(position, typeDefinition); } } model.put("schemaTypes", schemaTypes); NameForTypeDefinitionMethod nameForTypeDefinition = new NameForTypeDefinitionMethod( getTypeDefinitionNamePattern(), label, model.getNamespacesToPrefixes(), this.packageIdentifiers); model.put("nameForTypeDefinition", nameForTypeDefinition); model.put("nameForEnumConstant", new NameForEnumConstantMethod(getEnumConstantNamePattern(), label, model.getNamespacesToPrefixes(), this.packageIdentifiers)); TreeMap<String, String> conversions = new TreeMap<String, String>(); for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { if (typeDefinition.isEnum()) { conversions.put(typeDefinition.getQualifiedName(), "enum " + nameForTypeDefinition.calculateName(typeDefinition)); } else { conversions.put(typeDefinition.getQualifiedName(), (String) nameForTypeDefinition.calculateName(typeDefinition)); } } } ClientClassnameForMethod classnameFor = new ClientClassnameForMethod(conversions); model.put("classnameFor", classnameFor); model.put("functionIdentifierFor", new FunctionIdentifierForMethod(nameForTypeDefinition)); model.put("objcBaseName", label); model.put("separateCommonCode", isSeparateCommonCode()); model.put("findRootElement", new FindRootElementMethod()); model.put("referencedNamespaces", new ReferencedNamespacesMethod()); model.put("prefix", new PrefixMethod()); model.put("accessorOverridesAnother", new AccessorOverridesAnotherMethod()); debug("Generating the C data structures and (de)serialization functions..."); URL apiTemplate = getTemplateURL("api.fmt"); processTemplate(apiTemplate, model); } else { info("Skipping C code generation because everything appears up-to-date."); } ClientLibraryArtifact artifactBundle = new ClientLibraryArtifact(getName(), "objc.client.library", "Objective C Client Library"); NamedFileArtifact sourceHeader = new NamedFileArtifact(getName(), "objc.client.h", new File(getGenerateDir(), label + ".h")); sourceHeader.setPublic(false); sourceHeader.setArtifactType(ArtifactType.sources); NamedFileArtifact sourceImpl = new NamedFileArtifact(getName(), "objc.client.m", new File(getGenerateDir(), label + ".m")); sourceImpl.setPublic(false); sourceImpl.setArtifactType(ArtifactType.sources); String description = readResource("library_description.fmt"); //read in the description from file artifactBundle.setDescription(description); artifactBundle.addArtifact(sourceHeader); artifactBundle.addArtifact(sourceImpl); if (isSeparateCommonCode()) { NamedFileArtifact commonSourceHeader = new NamedFileArtifact(getName(), "objc.common.client.h", new File(getGenerateDir(), "enunciate-common.h")); commonSourceHeader.setPublic(false); commonSourceHeader.setArtifactType(ArtifactType.sources); commonSourceHeader.setDescription("Common header needed for all projects."); NamedFileArtifact commonSourceImpl = new NamedFileArtifact(getName(), "objc.common.client.m", new File(getGenerateDir(), "enunciate-common.m")); commonSourceImpl.setPublic(false); commonSourceImpl.setArtifactType(ArtifactType.sources); commonSourceImpl.setDescription("Common implementation code needed for all projects."); artifactBundle.addArtifact(commonSourceHeader); artifactBundle.addArtifact(commonSourceImpl); } getEnunciate().addArtifact(artifactBundle); } /** * Reads a resource into string form. * * @param resource The resource to read. * @return The string form of the resource. */ protected String readResource(String resource) throws IOException, EnunciateException { HashMap<String, Object> model = new HashMap<String, Object>(); ResourceMethod exampleResource = getModelInternal().findExampleResourceMethod(); String label = getLabel() == null ? getEnunciate().getConfig() == null ? "enunciate" : getEnunciate().getConfig().getLabel() : getLabel(); model.put("label", label); NameForTypeDefinitionMethod nameForTypeDefinition = new NameForTypeDefinitionMethod( getTypeDefinitionNamePattern(), label, getModelInternal().getNamespacesToPrefixes(), this.packageIdentifiers); if (exampleResource != null) { if (exampleResource.getEntityParameter() != null && exampleResource.getEntityParameter().getXmlElement() != null) { ElementDeclaration el = exampleResource.getEntityParameter().getXmlElement(); TypeDefinition typeDefinition = null; if (el instanceof RootElementDeclaration) { typeDefinition = getModelInternal().findTypeDefinition((RootElementDeclaration) el); } else if (el instanceof LocalElementDeclaration && ((LocalElementDeclaration) el).getElementTypeDeclaration() instanceof ClassDeclaration) { typeDefinition = getModelInternal().findTypeDefinition( (ClassDeclaration) ((LocalElementDeclaration) el).getElementTypeDeclaration()); } if (typeDefinition != null) { model.put("input_element_name", nameForTypeDefinition.calculateName(typeDefinition)); } } if (exampleResource.getRepresentationMetadata() != null && exampleResource.getRepresentationMetadata().getXmlElement() != null) { ElementDeclaration el = exampleResource.getRepresentationMetadata().getXmlElement(); TypeDefinition typeDefinition = null; if (el instanceof RootElementDeclaration) { typeDefinition = getModelInternal().findTypeDefinition((RootElementDeclaration) el); } else if (el instanceof LocalElementDeclaration && ((LocalElementDeclaration) el).getElementTypeDeclaration() instanceof ClassDeclaration) { typeDefinition = getModelInternal().findTypeDefinition( (ClassDeclaration) ((LocalElementDeclaration) el).getElementTypeDeclaration()); } if (typeDefinition != null) { model.put("output_element_name", nameForTypeDefinition.calculateName(typeDefinition)); } } model.put("resource_url", exampleResource.getFullpath()); model.put("resource_method", exampleResource.getHttpMethods().isEmpty() ? "GET" : exampleResource.getHttpMethods().iterator().next()); } URL res = ObjCDeploymentModule.class.getResource(resource); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); PrintStream out = new PrintStream(bytes); try { processTemplate(res, model, out); out.flush(); bytes.flush(); return bytes.toString("utf-8"); } catch (TemplateException e) { throw new EnunciateException(e); } } @Override protected ObjectWrapper getObjectWrapper() { return new DefaultObjectWrapper() { @Override public TemplateModel wrap(Object obj) throws TemplateModelException { if (obj instanceof JavaDoc) { return new FreemarkerJavaDoc((JavaDoc) obj); } return super.wrap(obj); } }; } /** * Get a template URL for the template of the given name. * * @param template The specified template. * @return The URL to the specified template. */ protected URL getTemplateURL(String template) { return ObjCDeploymentModule.class.getResource(template); } /** * The label for the Ruby API. * * @return The label for the Ruby API. */ public String getLabel() { return label; } /** * The label for the Ruby API. * * @param label The label for the Ruby API. */ public void setLabel(String label) { this.label = label; } /** * The package-to-module conversions. * * @return The package-to-module conversions. */ public Map<String, String> getPackageIdentifiers() { return packageIdentifiers; } /** * Add a client package conversion. * * @param conversion The conversion to add. */ public void addPackageIdentifier(PackageIdentifier conversion) { String name = conversion.getName(); String identifier = conversion.getIdentifier(); if (name == null) { throw new IllegalArgumentException("A 'name' attribute must be specified on a 'package' element."); } if (identifier == null) { throw new IllegalArgumentException("An 'identifer' attribute must be specified on 'package' element."); } this.packageIdentifiers.put(name, identifier); } /** * The format string creating a package identifier from a package name. * * @return The format string creating a package identifier from a package name. */ public String getPackageIdentifierPattern() { return packageIdentifierPattern; } /** * The format string creating a package identifier from a package name. * * @param packageIdentifierPattern The format string creating a package identifier from a package name. */ public void setPackageIdentifierPattern(String packageIdentifierPattern) { this.packageIdentifierPattern = packageIdentifierPattern; } /** * The pattern for converting a type definition to a unique C-style type name. * * @return The pattern for converting a type definition to a unique C-style type name. */ public String getTypeDefinitionNamePattern() { return typeDefinitionNamePattern; } /** * The pattern for converting a type definition to a unique C-style type name. * * @param typeDefinitionNamePattern The pattern for converting a type definition to a unique C-style type name. */ public void setTypeDefinitionNamePattern(String typeDefinitionNamePattern) { this.typeDefinitionNamePattern = typeDefinitionNamePattern; } /** * The pattern for converting an enum constant to a unique C-style type name. * * @return The pattern for converting an enum constant to a unique C-style type name. */ public String getEnumConstantNamePattern() { return enumConstantNamePattern; } /** * The pattern for converting an enum constant to a unique C-style type name. * * @param enumConstantNamePattern The pattern for converting an enum constant to a unique C-style type name. */ public void setEnumConstantNamePattern(String enumConstantNamePattern) { this.enumConstantNamePattern = enumConstantNamePattern; } /** * Whether to require this module (force-enable it). * * @return Whether to require this module (force-enable it). */ public boolean isForceEnable() { return forceEnable; } /** * Whether to require this module (force-enable it). * * @param forceEnable Whether to require this module (force-enable it). */ public void setForceEnable(boolean forceEnable) { this.forceEnable = forceEnable; } /** * What to translate 'id' to when writing out objective-c code. * * @return What to translate 'id' to when writing out objective-c code. */ public String getTranslateIdTo() { return translateIdTo; } /** * What to translate 'id' to when writing out objective-c code. * * @param translateIdTo What to translate 'id' to when writing out objective-c code. */ public void setTranslateIdTo(String translateIdTo) { this.translateIdTo = translateIdTo; } /** * Whether to separate the common code from the project-specific code. * * @return Whether to separate the common code from the project-specific code. */ public boolean isSeparateCommonCode() { return separateCommonCode; } /** * Whether to separate the common code from the project-specific code. * * @param separateCommonCode Whether to separate the common code from the project-specific code. */ public void setSeparateCommonCode(boolean separateCommonCode) { this.separateCommonCode = separateCommonCode; } /** * The set of facets to include. * * @return The set of facets to include. */ public Set<String> getFacetIncludes() { return facetIncludes; } /** * Add a facet include. * * @param name The name. */ public void addFacetInclude(String name) { if (name != null) { this.facetIncludes.add(name); } } /** * The set of facets to exclude. * * @return The set of facets to exclude. */ public Set<String> getFacetExcludes() { return facetExcludes; } /** * Add a facet exclude. * * @param name The name. */ public void addFacetExclude(String name) { if (name != null) { this.facetExcludes.add(name); } } @Override public RuleSet getConfigurationRules() { return new ObjCRuleSet(); } @Override public Validator getValidator() { return new ObjCValidator(this.translateIdTo); } // Inherited. @Override public boolean isDisabled() { if (isForceEnable()) { debug("Objective C module is force-enabled via the 'require' attribute in the configuration."); return false; } else if (super.isDisabled()) { return true; } else if (getModelInternal() != null && getModelInternal().getNamespacesToSchemas().isEmpty()) { debug("Objective C module is disabled because there are no schema types."); return true; } else if (getModelInternal() != null && getModelInternal().getRootResources().isEmpty()) { debug("Objective C module is disabled because there are no REST resources."); return true; } return false; } private static final class ExtensionDepthComparator implements Comparator<TypeDefinition> { public int compare(TypeDefinition t1, TypeDefinition t2) { int depth1 = 0; int depth2 = 0; ClassType superClass = t1.getSuperclass(); while (superClass != null && superClass.getDeclaration() != null && !Object.class.getName().equals(superClass.getDeclaration().getQualifiedName())) { depth1++; superClass = superClass.getDeclaration().getSuperclass(); } superClass = t2.getSuperclass(); while (superClass != null && superClass.getDeclaration() != null && !Object.class.getName().equals(superClass.getDeclaration().getQualifiedName())) { depth2++; superClass = superClass.getDeclaration().getSuperclass(); } return depth1 - depth2; } } }