001/**
002 * Copyright 2004-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.student.mock.mojo;
017
018import org.kuali.student.contract.model.Service;
019import org.kuali.student.contract.model.ServiceContractModel;
020import org.kuali.student.contract.model.ServiceMethod;
021import org.kuali.student.contract.model.ServiceMethodError;
022import org.kuali.student.contract.model.ServiceMethodParameter;
023import org.kuali.student.contract.model.XmlType;
024import org.kuali.student.contract.model.impl.ServiceContractModelPescXsdLoader;
025import org.kuali.student.contract.model.util.ModelFinder;
026import org.kuali.student.contract.writer.JavaClassWriter;
027import org.kuali.student.contract.writer.service.GetterSetterNameCalculator;
028import org.kuali.student.contract.writer.service.MessageStructureTypeCalculator;
029import org.kuali.student.contract.writer.service.ServiceExceptionWriter;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import java.util.*;
034
035/**
036 *
037 * @author nwright
038 */
039public class MockImplServiceWriter extends JavaClassWriter {
040    
041    private static Logger log = LoggerFactory.getLogger(MockImplServiceWriter.class);
042
043    //////////////////////////////
044    // Constants
045    //////////////////////////////
046
047    /**
048     * The standard type of methods used in our Service contract.
049     */
050    protected static enum MethodType {
051
052        VALIDATE,
053        CREATE,
054        CREATE_BULK,
055        ADD,
056        UPDATE,
057        UPDATE_OTHER,
058        DELETE,
059        REMOVE,
060        DELETE_OTHER,
061        GET_CREATE,
062        GET_BY_ID,
063        GET_BY_IDS,
064        RICE_GET_BY_NAMESPACE_AND_NAME,
065        GET_IDS_BY_TYPE,
066        GET_IDS_BY_OTHER,
067        GET_INFOS_BY_OTHER,
068        GET_TYPE,
069        GET_TYPES,
070        UNKNOWN
071    };
072
073    //////////////////////////////
074    // Data Variables
075    //////////////////////////////
076
077    protected ServiceContractModel model;
078    protected ModelFinder finder;
079    private String directory;
080    /**
081     * The package name is stored in the service object itself (the package spec kept
082     * moving around so I assumed the actual service name was unique but ran into a problem
083     * when we included rice because they have a StateService  meaning US states and we have
084     * a StateService meaning the state of the object so I added logic to detect rice and
085     * prepend that "RICE." to it
086     */
087    private String rootPackage;
088
089    /**
090     * Name of the service being operated on.
091     * If it is a RICE service it is prefixed with RICE.
092     * [11:32:18 AM] Norman Wright: short name... I think it gets it by taking the java class SimpleName and stripping off the word "Service" and I think making it lower case.
093     * [11:32:24 AM] Norman Wright: so OrganizationService becomes organization
094     */
095    protected String servKey;
096
097    protected List<ServiceMethod> methods;
098
099    /**
100     * A flag that holds if the service is an R1 service.
101     */
102    private boolean isR1;
103
104    //////////////////////////
105    // Constructor
106    //////////////////////////
107
108    public MockImplServiceWriter(ServiceContractModel model,
109            String directory,
110            String rootPackage,
111            String servKey,
112            List<ServiceMethod> methods,
113            boolean isR1) {
114        super(directory, calcPackage(servKey, rootPackage), calcClassName(servKey));
115        this.model = model;
116        this.finder = new ModelFinder(model);
117        this.directory = directory;
118        this.rootPackage = rootPackage;
119        this.servKey = servKey;
120        this.methods = methods;
121        this.isR1 = isR1;
122    }
123
124    public MockImplServiceWriter(ServiceContractModel model,
125                                 String directory,
126                                 String rootPackage,
127                                 String servKey,
128                                 List<ServiceMethod> methods,
129                                 boolean isR1,
130                                 String packageName,
131                                 String className) {
132        super(directory, packageName, className);
133        this.model = model;
134        this.finder = new ModelFinder(model);
135        this.directory = directory;
136        this.rootPackage = rootPackage;
137        this.servKey = servKey;
138        this.methods = methods;
139        this.isR1 = isR1;
140    }
141
142    /////////////////////////
143    // Functional Methods
144    /////////////////////////
145
146    /**
147     * Returns the mock implementation package name.
148     * @param servKey
149     * @param rootPackage
150     * @return
151     */
152    public static String calcPackage(String servKey, String rootPackage) {
153        String pack = rootPackage + ".";
154//        String pack = rootPackage + "." + servKey.toLowerCase() + ".";
155//  StringBuffer buf = new StringBuffer (service.getVersion ().length ());
156//  for (int i = 0; i < service.getVersion ().length (); i ++)
157//  {
158//   char c = service.getVersion ().charAt (i);
159//   c = Character.toLowerCase (c);
160//   if (Character.isLetter (c))
161//   {
162//    buf.append (c);
163//    continue;
164//   }
165//   if (Character.isDigit (c))
166//   {
167//    buf.append (c);
168//   }
169//  }
170//  pack = pack + buf.toString ();
171        pack = pack + "service.impl.map";
172        return pack;
173    }
174
175    /**
176     * Checks if this is a RICE service.
177     * @return true if this is a RICE service.
178     */
179    private boolean isRice() {
180        if (this.servKey.startsWith("RICE.")) {
181            return true;
182        }
183        return false;
184    }
185
186    protected static String fixServKey(String servKey) {
187        if (servKey.startsWith("RICE.")) {
188            return servKey.substring("RICE.".length());
189        }
190        return servKey;
191    }
192
193    /**
194     * Given the service key (name), returns a calculated class name for the mock impl.
195     */
196    public static String calcClassName(String servKey) {
197        return GetterSetterNameCalculator.calcInitUpper(fixServKey(servKey) + "ServiceMapImpl");
198    }
199
200    public static String calcServiceInterfaceClassName(String servKey) {
201        return GetterSetterNameCalculator.calcInitUpper(fixServKey(servKey) + "Service");
202    }
203
204    /**
205     * Analyses the method and returns a MethodType enum that describes what type of method this is.
206     */
207    protected MethodType calcMethodType(ServiceMethod method) {
208        if (this.isRice()) {
209            if (method.getName().contains("ByNamespaceCodeAndName")) {
210                return MethodType.RICE_GET_BY_NAMESPACE_AND_NAME;
211            }
212            if (method.getName().contains("ByNameAndNamespace")) {
213                return MethodType.RICE_GET_BY_NAMESPACE_AND_NAME;
214            }
215            if (method.getName().startsWith("get")) {
216                if (method.getParameters().size() == 1) {
217                    if (!method.getReturnValue().getType().endsWith("List")) {
218                        if (method.getParameters().get(0).getName().equals("id")) {
219                            return MethodType.GET_BY_ID;
220                        }
221
222                    } else {
223                        if (method.getParameters().get(0).getName().equals("ids")) {
224                            return MethodType.GET_BY_IDS;
225                        }
226                    }
227                }
228            }
229        }
230        if (method.getName().startsWith("validate")) {
231            return MethodType.VALIDATE;
232        }
233        if (method.getName().startsWith("create")) {
234            if (this.findInfoParameter(method) != null) {
235                return MethodType.CREATE;
236            }
237            return MethodType.CREATE_BULK;
238        }
239        if (method.getName().startsWith("add")) {
240            return MethodType.ADD;
241        }
242        if (method.getName().startsWith("update")) {
243            if (this.findInfoParameter(method) != null) {
244                return MethodType.UPDATE;
245            }
246            return MethodType.UPDATE_OTHER;
247        }
248        if (method.getName().startsWith("delete")) {
249            if (method.getName().contains("By")) {
250                if (!method.getName().startsWith("deleteBy")) {
251                    return MethodType.DELETE_OTHER;
252                }
253            }
254            if (method.getName().contains("For")) {
255                if (!method.getName().startsWith("deleteFor")) {
256                    return MethodType.DELETE_OTHER;
257                }
258            }
259            return MethodType.DELETE;
260        }
261        if (method.getName().startsWith("remove")) {
262            return MethodType.REMOVE;
263        }
264
265        if (method.getName().startsWith("getCreate")) {
266            return MethodType.GET_CREATE;
267        }
268
269        if (method.getName().startsWith("get")) {
270            if (method.getName().endsWith("ByIds")) {
271                return MethodType.GET_BY_IDS;
272            }
273            if (method.getName().endsWith("ByType")) {
274                return MethodType.GET_IDS_BY_TYPE;
275            }
276            if (method.getReturnValue().getType().endsWith("TypeInfo")) {
277                return MethodType.GET_TYPE;
278            }
279            if (method.getReturnValue().getType().endsWith("TypeInfoList")) {
280                return MethodType.GET_TYPES;
281            }
282            if (method.getName().endsWith("ByType")) {
283                return MethodType.GET_IDS_BY_TYPE;
284            }
285            if (method.getParameters().size() >= 1 && method.getParameters().size() <= 2) {
286                if (!method.getReturnValue().getType().endsWith("List")) {
287                    if (method.getParameters().get(0).getName().endsWith("Id")) {
288                        return MethodType.GET_BY_ID;
289                    }
290                }
291            }
292            if (method.getName().contains("By")) {
293                if (method.getReturnValue().getType().equals("StringList")) {
294                    return MethodType.GET_IDS_BY_OTHER;
295                }
296                if (method.getReturnValue().getType().endsWith("InfoList")) {
297                    return MethodType.GET_INFOS_BY_OTHER;
298                }
299            }
300        }
301
302        return MethodType.UNKNOWN;
303    }
304
305    /**
306     * Write out the entire file
307     */
308    public void write() {
309        indentPrint("public class " + calcClassName(servKey));
310        println(" implements MockService, " + calcServiceInterfaceClassName(servKey));
311        importsAdd ("org.kuali.student.common.mock.MockService");
312        Service serv = finder.findService(servKey);
313        importsAdd(serv.getImplProject() + "." + serv.getName());
314        openBrace();
315        // put all the cache variables at the top
316        indentPrintln("// cache variable ");
317        indentPrintln("// The LinkedHashMap is just so the values come back in a predictable order");
318        cacheVariablesWritten.clear();
319        for (ServiceMethod method : methods) {
320            MethodType methodType = calcMethodType(method);
321            switch (methodType) {
322                case CREATE:
323                case ADD:
324                case GET_TYPE:
325                case GET_BY_ID:
326                    writeCacheVariable(method);
327            }
328        }
329        println ("");
330        indentPrintln ("@Override");
331        indentPrintln ("public void clear()");
332        openBrace ();
333        cacheVariablesWritten.clear();
334         for (ServiceMethod method : methods) {
335            MethodType methodType = calcMethodType(method);
336            switch (methodType) {
337                case CREATE:
338                case ADD:
339                case GET_TYPE:
340                case GET_BY_ID:
341                    writeCacheVariableClear(method);
342            }
343        }
344        closeBrace ();
345        println ("");
346        
347        for (ServiceMethod method : methods) {
348            MethodType methodType = calcMethodType(method);
349            indentPrintln("");
350//            indentPrintln("/**");
351//            indentPrintWrappedComment(method.getDescription());
352//            indentPrintln("* ");
353//            for (ServiceMethodParameter param : method.getParameters()) {
354//                indentPrintWrappedComment("@param " + param.getName() + " - "
355//                        + param.getType() + " - "
356//                        + param.getDescription());
357//            }
358//            indentPrintWrappedComment("@return " + method.getReturnValue().
359//                    getDescription());
360//            indentPrintln("*/");
361            indentPrintln("@Override");
362            String type = method.getReturnValue().getType();
363            String realType = stripList(type);
364            indentPrint("public " + calcType(type, realType) + " " + method.getName()
365                    + "(");
366            // now do parameters
367            String comma = "";
368            for (ServiceMethodParameter param : method.getParameters()) {
369                type = param.getType();
370                realType = stripList(type);
371                print(comma);
372                
373                String calculatedType = realType;
374                
375                try {
376                                
377                        calculatedType = calcType(type, realType);
378                        
379                }
380                catch (Exception e) {
381                        log.warn(String.format("Method (%s) : failed to calculate type (%s), real type (%s)", method.getName(), type, realType), e);
382                }
383                
384                print(calculatedType);
385                
386                print(" ");
387                print(param.getName());
388                comma = ", ";
389            }
390            println(")");
391            // now do exceptions
392            comma = "throws ";
393            incrementIndent();
394            for (ServiceMethodError error : method.getErrors()) {
395                indentPrint(comma);
396                String exceptionClassName = calcExceptionClassName(error);
397                String exceptionPackageName = this.calcExceptionPackageName(error);
398                println(exceptionClassName);
399                this.importsAdd(exceptionPackageName + "." + exceptionClassName);
400                comma = "      ,";
401            }
402            decrementIndent();
403            openBrace();
404            indentPrintln("// " + methodType);
405            switch (methodType) {
406                case VALIDATE:
407                    writeValidate(method);
408                    break;
409                case CREATE:
410                    writeCreate(method);
411                    break;
412                case ADD:
413                    writeAdd(method);
414                    break;
415                case UPDATE:
416                    writeUpdate(method);
417                    break;
418                case DELETE:
419                    writeDelete(method);
420                    break;
421                case REMOVE:
422                    writeRemove(method);
423                    break;
424                case GET_BY_ID:
425                    writeGetById(method);
426                    break;
427                case GET_BY_IDS:
428                    writeGetByIds(method);
429                    break;
430                case GET_IDS_BY_TYPE:
431                    writeGetIdsByType(method);
432                    break;
433                case GET_IDS_BY_OTHER:
434                    writeGetIdsByOther(method);
435                    break;
436                case GET_INFOS_BY_OTHER:
437                    writeGetInfosByOther(method);
438                    break;
439                case GET_TYPE:
440                    writeGetType(method);
441                    break;
442                case GET_TYPES:
443                    writeGetTypes(method);
444                    break;
445                case RICE_GET_BY_NAMESPACE_AND_NAME:
446                    writeRiceGetByNamespaceAndName(method);
447                    break;
448                default:
449                    writeThrowsNotImplemented(method);
450            }
451            closeBrace();
452                
453        }
454
455        writeBoilerPlate();
456        closeBrace();
457
458        this.writeJavaClassAndImportsOutToFile();
459        this.getOut().close();
460    }
461
462    private String getInvalidParameterException() {
463        if (this.isRice()) {
464            return "RiceIllegalArgumentException";
465        }
466        return "InvalidParameterException";
467    }
468
469    private String getOperationFailedException() {
470        if (this.isRice()) {
471            return "RiceIllegalArgumentException";
472        }
473        return "OperationFailedException";
474    }
475
476    private String getDoesNotExistException() {
477        if (this.isRice()) {
478            return "RiceIllegalArgumentException";
479        }
480        return "DoesNotExistException";
481    }
482
483    private String getVersionMismatchException() {
484        if (this.isRice()) {
485            return "RiceIllegalStateException";
486        }
487        return "VersionMismatchException";
488    }
489
490    private void writeThrowsNotImplemented(ServiceMethod method) {
491        indentPrintln("throw new " + this.getOperationFailedException() + " (\"" + method.getName() + " has not been implemented\");");
492    }
493
494    protected String initLower(String str) {
495        return str.substring(0, 1).toLowerCase() + str.substring(1);
496    }
497
498    private void writeBoilerPlate() {
499        if (this.isRice()) {
500            return;
501        }
502        indentPrintln("");
503        indentPrintln("private StatusInfo newStatus() {");
504        indentPrintln("     StatusInfo status = new StatusInfo();");
505        indentPrintln("     status.setSuccess(Boolean.TRUE);");
506        indentPrintln("     return status;");
507        indentPrintln("}");
508        if (isR1) {
509            this.writeBoilerPlateR1();
510        } else {
511            this.writeBoilerPlateR2();
512        }
513    }
514
515    private void writeBoilerPlateR1() {
516        importsAdd("org.kuali.student.common.dto.MetaInfo");
517        indentPrintln("");
518
519        indentPrintln("private MetaInfo newMeta() {");
520        indentPrintln("     MetaInfo meta = new MetaInfo();");
521        indentPrintln("     meta.setCreateId(\"MOCKUSER\");");
522        importsAdd(Date.class.getName());
523        indentPrintln("     meta.setCreateTime(new Date());");
524        indentPrintln("     meta.setUpdateId(\"MOCKUSER\");");
525        indentPrintln("     meta.setUpdateTime(meta.getCreateTime());");
526        indentPrintln("     meta.setVersionInd(\"0\");");
527        indentPrintln("     return meta;");
528        indentPrintln("}");
529        indentPrintln("");
530        indentPrintln("private MetaInfo updateMeta(MetaInfo meta) {");
531        indentPrintln("     meta.setUpdateId(\"MOCKUSER\");");
532        indentPrintln("     meta.setUpdateTime(new Date());");
533        indentPrintln("     meta.setVersionInd((Integer.parseInt(meta.getVersionInd()) + 1) + \"\");");
534        indentPrintln("     return meta;");
535        indentPrintln("}");
536        indentPrintln("");
537
538    }
539
540    private void writeBoilerPlateR2() {
541        importsAdd("org.kuali.student.r2.common.dto.MetaInfo");
542        indentPrintln("");
543
544        indentPrintln("private MetaInfo newMeta(ContextInfo context) {");
545        indentPrintln("     MetaInfo meta = new MetaInfo();");
546        indentPrintln("     meta.setCreateId(context.getPrincipalId());");
547        importsAdd(Date.class.getName());
548        indentPrintln("     meta.setCreateTime(new Date());");
549        indentPrintln("     meta.setUpdateId(context.getPrincipalId());");
550        indentPrintln("     meta.setUpdateTime(meta.getCreateTime());");
551        indentPrintln("     meta.setVersionInd(\"0\");");
552        indentPrintln("     return meta;");
553        indentPrintln("}");
554        indentPrintln("");
555        indentPrintln("private MetaInfo updateMeta(MetaInfo old, ContextInfo context) {");
556        indentPrintln("     MetaInfo meta = new MetaInfo(old);");
557        indentPrintln("     meta.setUpdateId(context.getPrincipalId());");
558        indentPrintln("     meta.setUpdateTime(new Date());");
559        indentPrintln("     meta.setVersionInd((Integer.parseInt(meta.getVersionInd()) + 1) + \"\");");
560        indentPrintln("     return meta;");
561        indentPrintln("}");
562        indentPrintln("");
563
564    }
565
566    private void writeValidate(ServiceMethod method) {
567        indentPrintln("return new ArrayList<ValidationResultInfo> ();");
568        this.importsAdd(ArrayList.class.getName());
569    }
570    private Set<String> cacheVariablesWritten = new HashSet<String>();
571
572    private void writeCacheVariable(ServiceMethod method) {
573        String objectName = calcObjectName(method);
574        if (!this.isRice()) {
575            objectName = objectName + "Info";
576        }
577        String mapName = calcMapName(method);
578        if (cacheVariablesWritten.add(mapName)) {
579            indentPrintln("private Map<String, " + objectName + "> " + mapName + " = new LinkedHashMap<String, " + objectName + ">();");
580            importsAdd(Map.class.getName());
581            importsAdd(LinkedHashMap.class.getName());
582        }
583    }
584
585    private void writeCacheVariableClear(ServiceMethod method) {
586        String objectName = calcObjectName(method);
587        if (!this.isRice()) {
588            objectName = objectName + "Info";
589        }
590        String mapName = calcMapName(method);
591        if (cacheVariablesWritten.add(mapName)) {
592            indentPrintln("this." + mapName + ".clear ();");
593        }
594    }
595
596    private void writeCreate(ServiceMethod method) {
597        ServiceMethodParameter typeParam = this.findTypeParameter(method);
598        ServiceMethodParameter infoParam = this.findInfoParameter(method);
599        ServiceMethodParameter contextParam = this.findContextParameter(method);
600        String objectName = calcObjectName(method);
601        String infoName = objectName;
602        if (!this.isRice()) {
603            infoName = infoName + "Info";
604        }
605        String mapName = calcMapName(method);
606
607        if (this.isRice()) {
608            indentPrintln(infoName + " orig = this.get" + infoName + "ByNamespaceCodeAndName(" + infoParam.getName() + ".getNamespaceCode(), " + infoParam.getName() + ".getName());");
609            indentPrintln("if (orig != null)");
610            openBrace();
611            indentPrintln("throw new RiceIllegalArgumentException (" + infoParam.getName() + ".getNamespaceCode() + \".\" + " + infoParam.getName() + ".getName());");
612            closeBrace();
613        }
614        if (typeParam != null) {
615            indentPrintln("if (!" + typeParam.getName() + ".equals (" + infoParam.getName() + ".getTypeKey())) {");
616            indentPrintln("    throw new " + this.getInvalidParameterException() + " (\"The type parameter does not match the type on the info object\");");
617            indentPrintln("}");
618        }
619        if (method.getParameters().size() > 3) {
620            indentPrintln("// TODO: check the rest of the readonly fields that are specified on the create to make sure they match the info object");
621        }
622        if (this.isR1) {
623            indentPrintln("// don't have deep copy in R1 contracts so just use the object");
624            indentPrintln(infoName + " copy = " + infoParam.getName() + ";");
625        } else if (this.isRice()) {
626            indentPrintln(infoName + ".Builder copy = " + infoName + ".Builder.create (" + infoParam.getName() + ");");
627        } else {
628            indentPrintln(infoName + " copy = new " + infoName + "(" + infoParam.getName() + ");");
629        }
630        indentPrintln("if (copy.getId() == null) {");
631        // indentPrintln("   copy.setId(" + mapName + ".size() + \"\");");
632        importsAdd("org.kuali.student.common.util.UUIDHelper");
633        indentPrintln("   copy.setId(UUIDHelper.genStringUUID());");
634        indentPrintln("}");
635        if (contextParam != null) {
636            indentPrintln("copy.setMeta(newMeta(" + contextParam.getName() + "));");
637        }
638        if (isR1) {
639            indentPrintln(mapName + ".put(copy.getId(), copy);");
640            indentPrintln("// don't have deep copy in R1 contracts so just use the object");
641            indentPrintln("return copy;");
642        } else if (this.isRice()) {
643            indentPrintln (infoParam.getName() + " = copy.build ();");
644            indentPrintln(mapName + ".put(" + infoParam.getName() + ".getId(), " + infoParam.getName() + ");");
645            indentPrintln("return " + infoParam.getName() + ";");
646        } else {
647            indentPrintln(mapName + ".put(copy.getId(), copy);");
648            indentPrintln("return new " + infoName + "(copy);");
649        }
650    }
651
652    private void writeAdd(ServiceMethod method) {
653        ServiceMethodParameter typeParam = this.findTypeParameter(method);
654        ServiceMethodParameter infoParam = this.findInfoParameter(method);
655        ServiceMethodParameter contextParam = this.findContextParameter(method);
656        String objectName = calcObjectName(method);
657        String infoName = objectName;
658        if (!this.isRice()) {
659            infoName = infoName + "Info";
660        }
661        String mapName = calcMapName(method);
662
663        if (typeParam != null) {
664            indentPrintln("if (!" + typeParam.getName() + ".equals (" + infoParam.getName() + ".getTypeKey())) {");
665            indentPrintln("    throw new " + this.getInvalidParameterException() + " (\"The type parameter does not match the type on the info object\");");
666            indentPrintln("}");
667        }
668        if (method.getParameters().size() > 3) {
669            indentPrintln("// TODO: check the rest of the readonly fields that are specified on the create to make sure they match the info object");
670        }
671        if (isR1) {
672            indentPrintln("// don't have deep copy in R1 contracts so just use the object");
673            indentPrintln(infoName + " copy = " + infoParam.getName() + ";");
674        } else if (this.isRice()) {
675            indentPrintln(infoName + ".Builder copy = " + infoName + ".Builder.create (" + infoParam.getName() + ");");
676        } else {
677            indentPrintln(infoName + " copy = new " + infoName + "(" + infoParam.getName() + ");");
678        }
679        indentPrintln("if (copy.getId() == null) {");
680        // indentPrintln("   copy.setId(" + mapName + ".size() + \"\");");
681        importsAdd("org.kuali.student.common.util.UUIDHelper");
682        indentPrintln("   copy.setId(UUIDHelper.genStringUUID());");
683        indentPrintln("}");
684        if (contextParam != null) {
685            indentPrintln("copy.setMeta(newMeta(" + contextParam.getName() + "));");
686        }
687        if (isR1) {
688            indentPrintln(mapName + ".put(copy.getId(), copy);");
689            indentPrintln("// don't have deep copy in R1 contracts so just use the object");
690            indentPrintln("return copy;");
691        } else if (this.isRice()) {
692            indentPrintln (infoParam.getName() + " = copy.build ();");
693            indentPrintln(mapName + ".put(" + infoParam.getName() + ".getId(), " + infoParam.getName() + ");");
694            indentPrintln("return " + infoParam.getName() + ";");
695        } else {
696            indentPrintln(mapName + ".put(copy.getId(), copy);");
697            indentPrintln("return new " + infoName + "(copy);");
698        }
699    }
700
701    private ServiceMethodParameter findIdParameter(ServiceMethod method) {
702        String idFieldName = calcObjectName(method) + "Id";
703        for (ServiceMethodParameter parameter : method.getParameters()) {
704            if (parameter.getType().equals("String")) {
705                if (parameter.getName().equals(idFieldName)) {
706                    return parameter;
707                }
708            }
709        }
710
711        // if only one parameter and it is a string then grab that
712        if (method.getParameters().size() == 1) {
713            for (ServiceMethodParameter parameter : method.getParameters()) {
714                if (parameter.getType().equals("String")) {
715                    return parameter;
716                }
717            }
718        }
719        // can't find name exactly 
720        for (ServiceMethodParameter parameter : method.getParameters()) {
721            if (parameter.getType().equals("String")) {
722                if (parameter.getName().endsWith("Id")) {
723                    return parameter;
724                }
725            }
726        }
727        // can't find name exactly try key 
728        for (ServiceMethodParameter parameter : method.getParameters()) {
729            if (parameter.getType().equals("String")) {
730                if (!parameter.getName().endsWith("TypeKey")) {
731                    if (parameter.getName().endsWith("Key")) {
732                        return parameter;
733                    }
734                }
735            }
736        }
737        log.warn("Could not find the Id paramter for " + method.getService() + "." + method.getName() + " so returning the first one");
738        return method.getParameters().get(0);
739    }
740
741    private ServiceMethodParameter findContextParameter(ServiceMethod method) {
742        for (ServiceMethodParameter parameter : method.getParameters()) {
743            if (parameter.getType().equals("ContextInfo")) {
744                return parameter;
745            }
746        }
747        return null;
748    }
749
750    private ServiceMethodParameter findInfoParameter(ServiceMethod method) {
751        String objectName = calcObjectName(method);
752        if (!this.isRice()) {
753            objectName = objectName + "Info";
754        }
755        for (ServiceMethodParameter parameter : method.getParameters()) {
756            if (parameter.getType().equals(objectName)) {
757                return parameter;
758            }
759        }
760        if (method.getParameters().size() >= 1) {
761            return method.getParameters().get(0);
762        }
763        return null;
764    }
765
766    private ServiceMethodParameter findTypeParameter(ServiceMethod method) {
767        for (ServiceMethodParameter parameter : method.getParameters()) {
768            if (parameter.getType().equals("String")) {
769                if (parameter.getName().endsWith("TypeKey")) {
770                    return parameter;
771                }
772                if (parameter.getName().endsWith("Type")) {
773                    return parameter;
774                }
775            }
776        }
777        return null;
778    }
779
780    private String calcMapName(ServiceMethod method) {
781        String mapName = this.calcObjectName(method);
782        mapName = this.initLower(mapName) + "Map";
783        return mapName;
784    }
785
786    protected String calcObjectName(ServiceMethod method) {
787        if (method.getName().startsWith("create")) {
788            return method.getName().substring("create".length());
789        }
790        if (method.getName().startsWith("update")) {
791            return method.getName().substring("update".length());
792        }
793        if (method.getName().startsWith("validate")) {
794            return method.getName().substring("validate".length());
795        }
796        if (method.getName().startsWith("delete")) {
797            return method.getName().substring("delete".length());
798        }
799        if (method.getName().startsWith("get")) {
800            if (method.getReturnValue().getType().equals("StringList")) {
801                if (method.getName().contains("IdsBy")) {
802                    return method.getName().substring("get".length(),
803                            method.getName().indexOf("IdsBy"));
804                }
805                if (method.getName().contains("IdsFor")) {
806                    return method.getName().substring("get".length(),
807                            method.getName().indexOf("IdsFor"));
808                }
809                return method.getName().substring("get".length());
810            }
811            String name = method.getReturnValue().getType();
812            if (name.endsWith("List")) {
813                name = name.substring(0, name.length() - "List".length());
814            }
815            if (name.endsWith("Info")) {
816                name = name.substring(0, name.length() - "Info".length());
817            }
818            return name;
819        }
820        if (method.getName().startsWith("add")) {
821            return method.getName().substring("add".length());
822        }
823        if (method.getName().startsWith("remove")) {
824            return method.getName().substring("remove".length());
825        }
826        String returnType = this.stripList(method.getReturnValue().getType());
827        XmlType type = this.finder.findXmlType(returnType);
828        if (type.getPrimitive().equals(XmlType.COMPLEX)) {
829            return returnType;
830        }
831        throw new IllegalArgumentException(method.getName());
832    }
833
834    private void writeUpdate(ServiceMethod method) {
835        ServiceMethodParameter idParam = this.findIdParameter(method);
836        ServiceMethodParameter infoParam = this.findInfoParameter(method);
837        ServiceMethodParameter contextParam = this.findContextParameter(method);
838        if (infoParam == null) {
839            throw new NullPointerException(method.getName());
840        }
841        String objectName = calcObjectName(method);
842        String infoName = objectName;
843        if (!this.isRice()) {
844            infoName = infoName + "Info";
845        }
846        String mapName = calcMapName(method);
847        if (idParam != null) {
848            if (!this.isRice()) {
849                indentPrintln("if (!" + idParam.getName() + ".equals (" + infoParam.getName() + ".getId())) {");
850                indentPrintln("    throw new " + this.getInvalidParameterException() + " (\"The id parameter does not match the id on the info object\");");
851                indentPrintln("}");
852            }
853        }
854        if (isR1) {
855            indentPrintln("// don't have deep copy in R1 contracts so just use the object");
856            indentPrintln(infoName + " copy = " + infoParam.getName() + ";");
857        } else if (this.isRice()) {
858            indentPrintln(infoName + ".Builder copy = " + infoName + ".Builder.create (" + infoParam.getName() + ");");
859        } else {
860            indentPrintln(infoName + " copy = new " + infoName + "(" + infoParam.getName() + ");");
861        }
862        if (contextParam != null) {
863            indentPrintln(infoName + " old = this.get" + objectName + "(" + infoParam.getName() + ".getId(), " + contextParam.getName() + ");");
864        } else {
865            indentPrintln(infoName + " old = this.get" + objectName + "(" + infoParam.getName() + ".getId());");
866        }
867        if (isR1) {
868            indentPrintln("if (!old.getMetaInfo().getVersionInd().equals(copy.getMetaInfo().getVersionInd())) {");
869            indentPrintln("    throw new " + this.getVersionMismatchException() + "(old.getMetaInfo().getVersionInd());");
870            indentPrintln("}");
871            if (contextParam != null) {
872                indentPrintln("copy.setMeta(updateMeta(copy.getMetaInfo()));");
873            }
874        } else if (this.isRice()) {
875            indentPrintln("if (!old.getVersionNumber().equals(copy.getVersionNumber())) {");
876            indentPrintln("    throw new " + this.getVersionMismatchException() + "(\"\" + old.getVersionNumber());");
877            indentPrintln("}");
878            indentPrintln("copy.setVersionNumber(copy.getVersionNumber() + 1);");
879        } else {
880            indentPrintln("if (!old.getMeta().getVersionInd().equals(copy.getMeta().getVersionInd())) {");
881            indentPrintln("    throw new " + this.getVersionMismatchException() + "(old.getMeta().getVersionInd());");
882            indentPrintln("}");
883            if (contextParam != null) {
884                indentPrintln("copy.setMeta(updateMeta(copy.getMeta(), contextInfo));");
885            }
886        }
887        if (isR1) {
888            indentPrintln("this." + mapName + " .put(" + infoParam.getName() + ".getId(), copy);");
889            indentPrintln("// don't have deep copy in R1 contracts so just use the object");
890            indentPrintln("return copy;");
891        } else if (this.isRice()) {
892            indentPrintln (infoParam.getName() + " = copy.build ();");
893            indentPrintln("this." + mapName + " .put(" + infoParam.getName() + ".getId(), " + infoParam.getName() + ");");
894            indentPrintln("return " + infoParam.getName() + ";");
895        } else {
896            indentPrintln("this." + mapName + " .put(" + infoParam.getName() + ".getId(), copy);");
897            indentPrintln("return new " + infoName + "(copy);");
898        }
899
900    }
901
902    private void writeDelete(ServiceMethod method) {
903        ServiceMethodParameter idParam = this.findIdParameter(method);
904        String mapName = calcMapName(method);
905        indentPrintln("if (this." + mapName + ".remove(" + idParam.getName() + ") == null) {");
906        indentPrintln("   throw new " + this.getOperationFailedException() + "(" + idParam.getName() + ");");
907        indentPrintln("}");
908        indentPrintln("return newStatus();");
909    }
910
911    private void writeRemove(ServiceMethod method) {
912        ServiceMethodParameter idParam = this.findIdParameter(method);
913        String mapName = calcMapName(method);
914        indentPrintln("if (this." + mapName + ".remove(" + idParam.getName() + ") == null) {");
915        indentPrintln("   throw new " + this.getOperationFailedException() + "(" + idParam.getName() + ");");
916        indentPrintln("}");
917        indentPrintln("return newStatus();");
918    }
919
920    private void writeGetById(ServiceMethod method) {
921        ServiceMethodParameter idParam = this.findIdParameter(method);
922        String mapName = calcMapName(method);
923        indentPrintln("if (!this." + mapName + ".containsKey(" + idParam.getName() + ")) {");
924        indentPrintln("   throw new " + this.getDoesNotExistException() + "(" + idParam.getName() + ");");
925        indentPrintln("}");
926        String objectName = calcObjectName(method);
927        String infoName = objectName;
928        if (!this.isRice()) {
929            infoName = infoName + "Info";
930        }
931        if (isR1) {
932            indentPrintln("// r1 objects do not have deep cody");
933            indentPrintln("return this." + mapName + ".get (" + idParam.getName() + ");");
934        } else if (this.isRice()) {
935            indentPrintln("return this." + mapName + ".get (" + idParam.getName() + ");");
936        } else {
937            indentPrintln("return new " + infoName + "(this." + mapName + ".get (" + idParam.getName() + "));");
938        }
939
940    }
941
942    private void writeGetByIds(ServiceMethod method) {
943        String objectName = this.calcObjectName(method);
944        String infoName = objectName;
945        if (!this.isRice()) {
946            infoName = infoName + "Info";
947        }
948        ServiceMethodParameter idListParam = this.findIdListParameter(method);
949        ServiceMethodParameter contextParam = this.findContextParameter(method);
950        this.importsAdd(ArrayList.class.getName());
951        indentPrintln("List<" + infoName + "> list = new ArrayList<" + infoName + "> ();");
952
953        indentPrintln("for (String id: " + idListParam.getName() + ") {");
954        if (this.isRice()) {
955            indentPrintln("    list.add (this.get" + objectName + "(id));");
956        } else {
957            indentPrintln("    list.add (this.get" + objectName + "(id, " + contextParam.getName() + "));");
958        }
959        indentPrintln("}");
960        indentPrintln("return list;");
961    }
962
963    private void writeGetIdsByOther(ServiceMethod method) {
964        String objectName = this.calcObjectName(method);
965        String infoName = objectName;
966        if (!this.isRice()) {
967            infoName = infoName + "Info";
968        }
969        String mapName = calcMapName(method);
970        this.importsAdd(ArrayList.class.getName());
971        indentPrintln("List<String> list = new ArrayList<String> ();");
972        indentPrintln("for (" + infoName + " info: " + mapName + ".values ()) {");
973        for (ServiceMethodParameter parameter : method.getParameters()) {
974            if (parameter.getType().equals("ContextInfo")) {
975                continue;
976            }
977            incrementIndent();
978            indentPrintln("if (" + parameter.getName() + ".equals(info.get" + initUpper(parameter.getName()) + "())) {");
979        }
980        indentPrintln("    list.add (info.getId ());");
981        for (ServiceMethodParameter parameter : method.getParameters()) {
982            if (parameter.getType().equals("ContextInfo")) {
983                continue;
984            }
985            indentPrintln("}");
986            decrementIndent();
987        }
988        indentPrintln("}");
989        indentPrintln("return list;");
990    }
991
992    private void writeGetIdsByType(ServiceMethod method) {
993        String objectName = this.calcObjectName(method);
994        String infoName = objectName;
995        if (!this.isRice()) {
996            infoName = infoName + "Info";
997        }
998        String mapName = calcMapName(method);
999        this.importsAdd(ArrayList.class.getName());
1000        indentPrintln("List<String> list = new ArrayList<String> ();");
1001        indentPrintln("for (" + infoName + " info: " + mapName + ".values ()) {");
1002        for (ServiceMethodParameter parameter : method.getParameters()) {
1003            if (parameter.getType().equals("ContextInfo")) {
1004                continue;
1005            }
1006            incrementIndent();
1007            indentPrintln("if (" + parameter.getName() + ".equals(info.getTypeKey())) {");
1008        }
1009        indentPrintln("    list.add (info.getId ());");
1010        for (ServiceMethodParameter parameter : method.getParameters()) {
1011            if (parameter.getType().equals("ContextInfo")) {
1012                continue;
1013            }
1014            indentPrintln("}");
1015            decrementIndent();
1016        }
1017        indentPrintln("}");
1018        indentPrintln("return list;");
1019    }
1020
1021    private void writeRiceGetByNamespaceAndName(ServiceMethod method) {
1022        String objectName = this.calcObjectName(method);
1023        String infoName = objectName;
1024        if (!this.isRice()) {
1025            infoName = infoName + "Info";
1026        }
1027        String mapName = calcMapName(method);
1028        indentPrintln("for (" + infoName + " info: " + mapName + ".values ()) {");
1029        for (ServiceMethodParameter parameter : method.getParameters()) {
1030            incrementIndent();
1031            indentPrintln("if (" + parameter.getName() + ".equals(info.get" + initUpper(parameter.getName()) + "())) {");
1032        }
1033        indentPrintln("    return " + infoName + ".Builder.create (info).build ();");
1034        for (ServiceMethodParameter parameter : method.getParameters()) {
1035            indentPrintln("}");
1036            decrementIndent();
1037        }
1038        indentPrintln("}");
1039        indentPrintln("throw new RiceIllegalArgumentException ();");
1040    }
1041
1042    private void writeGetInfosByOther(ServiceMethod method) {
1043        String objectName = this.calcObjectName(method);
1044        String infoName = objectName;
1045        if (!this.isRice()) {
1046            infoName = infoName + "Info";
1047        }
1048        String mapName = calcMapName(method);
1049        this.importsAdd(ArrayList.class.getName());
1050        indentPrintln("List<" + infoName + "> list = new ArrayList<" + infoName + "> ();");
1051        indentPrintln("for (" + infoName + " info: " + mapName + ".values ()) {");
1052        for (ServiceMethodParameter parameter : method.getParameters()) {
1053            if (parameter.getType().equals("ContextInfo")) {
1054                continue;
1055            }
1056            incrementIndent();
1057            indentPrintln("if (" + parameter.getName() + ".equals(info.get" + initUpper(parameter.getName()) + "())) {");
1058        }
1059        indentPrintln("    list.add (new " + infoName + "(info));");
1060        for (ServiceMethodParameter parameter : method.getParameters()) {
1061            if (parameter.getType().equals("ContextInfo")) {
1062                continue;
1063            }
1064            indentPrintln("}");
1065            decrementIndent();
1066        }
1067        indentPrintln("}");
1068        indentPrintln("return list;");
1069    }
1070
1071    private void writeGetType(ServiceMethod method) {
1072        ServiceMethodParameter idParam = this.findIdParameter(method);
1073        String mapName = calcMapName(method);
1074        indentPrintln("if (!this." + mapName + ".containsKey(" + idParam.getName() + ")) {");
1075        indentPrintln("   throw new " + this.getOperationFailedException() + "(" + idParam.getName() + ");");
1076        indentPrintln("}");
1077        indentPrintln("return this." + mapName + ".get (" + idParam.getName() + ");");
1078    }
1079
1080    private void writeGetTypes(ServiceMethod method) {
1081        String mapName = calcMapName(method);
1082        String objectName = this.calcObjectName(method);
1083        String infoName = objectName;
1084        if (!this.isRice()) {
1085            infoName = infoName + "Info";
1086        }
1087        this.importsAdd(ArrayList.class.getName());
1088        indentPrintln("return new ArrayList<" + infoName + ">(" + mapName + ".values ());");
1089    }
1090
1091    private String initUpper(String str) {
1092        return str.substring(0, 1).toUpperCase() + str.substring(1);
1093    }
1094
1095    private ServiceMethodParameter findIdListParameter(ServiceMethod method) {
1096        String idFieldName = calcObjectName(method) + "Ids";
1097        if (this.isRice()) {
1098            idFieldName = "ids";
1099        }
1100        for (ServiceMethodParameter parameter : method.getParameters()) {
1101            if (parameter.getType().equals("StringList")) {
1102                if (parameter.getName().equals(idFieldName)) {
1103                    return parameter;
1104                }
1105            }
1106        }
1107        // can't find name exactly 
1108        for (ServiceMethodParameter parameter : method.getParameters()) {
1109            if (parameter.getType().equals("StringList")) {
1110                if (parameter.getName().endsWith("Ids")) {
1111                    return parameter;
1112                }
1113            }
1114        }
1115        // can't find name exactly try key 
1116        for (ServiceMethodParameter parameter : method.getParameters()) {
1117            if (parameter.getType().equals("StringList")) {
1118                if (parameter.getName().endsWith("Keys")) {
1119                    return parameter;
1120                }
1121            }
1122        }
1123        return null;
1124    }
1125
1126    private String stripList(String str) {
1127        return GetterSetterNameCalculator.stripList(str);
1128    }
1129
1130    private String calcExceptionClassName(ServiceMethodError error) {
1131        if (error.getClassName() == null) {
1132            return ServiceExceptionWriter.calcClassName(error.getType());
1133        }
1134        return error.getClassName();
1135    }
1136
1137    private String calcExceptionPackageName(ServiceMethodError error) {
1138        if (error.getClassName() == null) {
1139            return ServiceExceptionWriter.calcPackage(rootPackage);
1140        }
1141        return error.getPackageName();
1142    }
1143
1144    private String calcType(String type, String realType) {
1145        XmlType t = finder.findXmlType(this.stripList(type));
1146        String retType = MessageStructureTypeCalculator.calculate(this, model, type, realType,
1147                t.getJavaPackage());
1148        if (this.isRice()) {
1149            if (retType.equals("Boolean")) {
1150                retType = "boolean";
1151            }
1152            if (retType.equals("Void")) {
1153                retType = "void";
1154            }
1155        }
1156        return retType;
1157    }
1158}