/** * Returns a JavaParameter for a given property. * * @param name Name * @param p Property * @param namespace Namespace * @param klass Class * @return Parameter */ private JavaAttribute getParam(String name, Property p, Namespace namespace, JavaClass klass) { final JavaAttribute jp; final PropertyController pc = new PropertyController(name, p); if (p.isEnum()) { final JavaEnum e = pc.getEnum(namespace, name); jp = new JavaAttribute(name, e, klass); klass.linkInnerEnum(e); } else { final JavaClass type = pc.getClass(namespace, name, klass); jp = new JavaAttribute(name, type, klass); type.setUsedAsParameter(); if (type.isInner()) { final String k = type.getName(); if (innerClassDupes.containsKey(k)) { if (innerClassDupes.get(k) != null) { // update "old" type with new name and set null innerClassDupes.get(k).suffixName(getSuffixFromMembers(innerClassDupes.get(k))); innerClassDupes.put(k, null); } // update type with new name type.suffixName(getSuffixFromMembers(type)); } else { innerClassDupes.put(k, type); } klass.linkInnerType(type); } } jp.setDescription(p.getDescription()); jp.setRequired(p.isRequired()); jp.resolveType(); return jp; }
/** * Creates the agnostic {@link JavaClass} object. * * @param namespace Namespace * @param methodName Name of the class (retrieved from parent key) * @return Class object */ public JavaMethod getClass(Namespace namespace, String methodName) { final JavaMethod klass = new JavaMethod(namespace, name, apiType); final List<JavaConstructor> constructors = new ArrayList<JavaConstructor>(); constructors.add(new JavaConstructor(klass)); JavaAttribute properties = null; // 1. parameters for (Param p : method.getParams()) { if (p.isEnum()) { final JavaAttribute jp = getParam(p.getName(), p, namespace, klass); // add parameter to all constructor for (JavaConstructor jc : constructors) { jc.addParameter(jp); } } else if (p.isArray() && p.getItems().isEnum()) { throw new UnsupportedOperationException( "No support for params that are arrays of enums yet."); } else { final TypeWrapper tr = p.getType(); // single type if (!p.isMultitype() || Helper.equalNativeTypes(tr.getList())) { final JavaAttribute jp = getParam(p.getName(), p, namespace, klass); // "properties" is a special case, we add them at the end. if (p.getName().equals("properties")) { properties = jp; continue; } // add parameter to all constructor for (JavaConstructor jc : constructors) { jc.addParameter(jp); } // multitype } else { // copy current constructors so we can copy them for each additional type final List<JavaConstructor> copiedConstructors = new ArrayList<JavaConstructor>(); for (JavaConstructor jc : constructors) { copiedConstructors.add(jc.copy()); } int i = 0; for (Type t : tr.getList()) { if (i == 0) { // first multitype: just add param to current constructors. final JavaAttribute jp = getParam(p.getName(), t, namespace, klass); jp.setRequired(p.isRequired()); for (JavaConstructor jc : constructors) { jc.addParameter(jp); } } else { // second..nth multitype: for each previously saved constructor, copy then add param final JavaAttribute jp = getParam(p.getName(), t, namespace, klass); jp.setRequired(p.isRequired()); for (JavaConstructor jc : copiedConstructors) { final JavaConstructor jjc = jc.copy(); jjc.addParameter(jp); boolean dupeFound = false; for (JavaConstructor jjjc : constructors) { if (jjjc.hasSameParams(jjc)) { dupeFound = true; } } if (!dupeFound) { constructors.add(jjc); } } } i++; } } } } // create constructor aliases without non-required args final int size = constructors.size(); for (int j = 0; j < size; j++) { final JavaConstructor jc = constructors.get(j); final int numParams = jc.getParameters().size(); if (numParams > 5) { continue; } final long n = (int) Math.pow(2, numParams) - 2; // l is a bitmask and counting up means getting all combinations for (Long l = 0L; l < n; l++) { final JavaConstructor alias = new JavaConstructor(jc.getType()); // retrieve params for (int i = 0; i < numParams; i++) { long b = ((l >> i) & 1); // bit at position i final JavaAttribute param = jc.getParameters().get(i); if (b == 1 || param.isRequired()) { alias.addParameter(param); } } // check signature already there boolean found = false; for (JavaConstructor jjc : constructors) { if (jjc.hasSameParams(alias)) { found = true; } } if (!found) { constructors.add(alias); } } } // add properties if previously skipped if (properties != null) { for (JavaConstructor jc : constructors) { jc.addParameter(properties); } } klass.setConstructors(constructors); // 2. return type final TypeWrapper tw = method.getReturns(); if (tw.isObject()) { final Type type = tw.getObj(); // result type is either native, array, a type reference... if (type.isNative() || type.isRef() || type.isArray()) { final String name = klass.getName() + RESULT_CLASS_SUFFIX; final PropertyController returnTypeController = new PropertyController(null, method.getReturns().getObj()); final JavaClass returnType = returnTypeController.getClass(namespace, name, klass); if (returnType.isTypeArray()) { klass.linkInnerType(returnType.getArrayType()); } klass.setReturnType(returnType); // ...or an object } else if (type.isObjectDefinition()) { /* * In case of an object, there are two scenarios. Either the * the object is a "meta" object, meaning it contains meta data * such as the limits of the result. In this case there is * one non-meta property where the data is stored. Example: * "returns" : { * "properties" : { * "limits" : { "$ref" : "List.LimitsReturned", "required" : true }, * "sources" : { "$ref" : "List.Items.Sources", "required" : true } * }, * "type" : "object" * } * If there is more than one "non-meta" property, we assume * it's a full-fledged object definition which will result in * an inner class: * "returns" : { * "properties" : { * "details" : { "description" : "Tran...", "required" : true, "type" : "any" }, * "mode" : { "description" : "Dir...", "enums" : ["redirect", "direct"], "required" : true, "type" : "string" }, * "protocol" : { "enums" : ["http"], "required" : true, "type" : "string" } * }, * "type" : "object" * } */ if (!type.hasProperties() && !type.hasAdditionalProperties()) { throw new IllegalStateException( "Definition is object but no props defined. That's seriously weird."); } if (type.hasProperties()) { // go through props and compare and count. String potentialResultPropName = null; // the non-meta prop in case there is only one. int nonMetaProps = 0; for (String propName : type.getProperties().keySet()) { if (!META_RETURN_PROPS.contains(propName)) { potentialResultPropName = propName; nonMetaProps++; } } // first case described above: data is wrapped into a meta object. if (nonMetaProps == 1) { final Property prop = type.getProperties().get(potentialResultPropName); if (!prop.isRef() && !prop.isNative() && !prop.isArray()) { throw new IllegalStateException( "Return type is expected to be either reference, native or array"); } final PropertyController returnTypeController = new PropertyController(null, prop); klass.setReturnType(returnTypeController.getClass(namespace, null, klass)); klass.setReturnProperty(potentialResultPropName); // second case: full object definition. we suffix the class name with // RESULT_CLASS_SUFFIX } else { final String name = klass.getName() + RESULT_CLASS_SUFFIX; final PropertyController returnTypeController = new PropertyController(null, type); final JavaClass returnType = returnTypeController.getClass(namespace, name, klass); klass.setReturnType(returnType); klass.getInnerTypes().add(returnType); } } else { throw new UnsupportedOperationException( "Naked objects with only additional attributes are not yet supported."); } } else { throw new IllegalStateException( "Result type is expected to be a reference, native or an object."); } } else { throw new RuntimeException("Expected return type is an object with properties."); } // description klass.setDescription(method.getDescription()); return klass; }