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     */
016    package org.kuali.student.mock.mojo;
017    
018    import org.kuali.student.contract.model.Service;
019    import org.kuali.student.contract.model.ServiceContractModel;
020    import org.kuali.student.contract.model.ServiceMethod;
021    import org.kuali.student.contract.model.ServiceMethodError;
022    import org.kuali.student.contract.model.ServiceMethodParameter;
023    import org.kuali.student.contract.model.XmlType;
024    import org.kuali.student.contract.model.impl.ServiceContractModelPescXsdLoader;
025    import org.kuali.student.contract.model.util.ModelFinder;
026    import org.kuali.student.contract.writer.JavaClassWriter;
027    import org.kuali.student.contract.writer.service.GetterSetterNameCalculator;
028    import org.kuali.student.contract.writer.service.MessageStructureTypeCalculator;
029    import org.kuali.student.contract.writer.service.ServiceExceptionWriter;
030    import org.slf4j.Logger;
031    import org.slf4j.LoggerFactory;
032    
033    import java.util.*;
034    
035    /**
036     *
037     * @author nwright
038     */
039    public 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.mock";
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) + "ServiceMockImpl");
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    }