/** * Copyright (C) 2005 - 2012 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.jdt.command.bean; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.eclim.Services; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.plugin.core.util.TemplateUtils; import org.eclim.plugin.jdt.PluginResources; import org.eclim.plugin.jdt.util.JavaUtils; import org.eclim.plugin.jdt.util.MethodUtils; import org.eclim.plugin.jdt.util.TypeInfo; import org.eclim.plugin.jdt.util.TypeUtils; import org.eclim.util.file.Position; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.formatter.CodeFormatter; /** * Command used to generate java bean property methods (getters / setters). * * @author Eric Van Dewoestine */ @Command( name = "java_bean_properties", options = "REQUIRED p project ARG," + "REQUIRED f file ARG," + "REQUIRED o offset ARG," + "OPTIONAL e encoding ARG," + "REQUIRED r properties ARG," + "REQUIRED t type ARG," + "OPTIONAL i indexed NOARG" ) public class PropertiesCommand extends AbstractCommand { private static final String GETTER_TEMPLATE = "getter.gst"; private static final String SETTER_TEMPLATE = "setter.gst"; private static final String GETTER = "getter"; private static final String SETTER = "setter"; private static final int TYPE_GET = 0; private static final int TYPE_GET_INDEX = 1; private static final int TYPE_SET = 2; private static final int TYPE_SET_INDEX = 3; private static final String INT_SIG = Signature.createTypeSignature("int", true); private static final String[] INT_ARG = new String[]{INT_SIG}; /** * {@inheritDoc} */ public Object execute(CommandLine commandLine) throws Exception { String project = commandLine.getValue(Options.PROJECT_OPTION); String file = commandLine.getValue(Options.FILE_OPTION); String methods = commandLine.getValue(Options.TYPE_OPTION); String[] properties = StringUtils.split( commandLine.getValue(Options.PROPERTIES_OPTION), ','); int offset = getOffset(commandLine); boolean indexed = commandLine.hasOption(Options.INDEXED_OPTION); ICompilationUnit src = JavaUtils.getCompilationUnit(project, file); IType type = TypeUtils.getType(src, offset); List<IField> fields = Arrays.asList(type.getFields()); IJavaElement sibling = null; // insert in reverse order since eclipse IType only has an insert before, // no insert after. for(int ii = properties.length - 1; ii >= 0; ii--){ IField field = type.getField(properties[ii]); if(field != null){ boolean array = false; if(Signature.getArrayCount(field.getTypeSignature()) > 0){ array = true; } if(methods.indexOf(SETTER) != -1){ // index setter if(array && indexed){ sibling = getSibling(type, fields, field, sibling, TYPE_SET_INDEX); sibling = insertSetter(src, type, sibling, field, true); } // setter sibling = getSibling(type, fields, field, sibling, TYPE_SET); sibling = insertSetter(src, type, sibling, field, false); } if(methods.indexOf(GETTER) != -1){ // index getter if(array && indexed){ sibling = getSibling(type, fields, field, sibling, TYPE_GET_INDEX); sibling = insertGetter(src, type, sibling, field, true); } // getter sibling = getSibling(type, fields, field, sibling, TYPE_GET); sibling = insertGetter(src, type, sibling, field, false); } } } return null; } /** * Insert a getter for the supplied property. * * @param src The src file. * @param type The type to insert into. * @param sibling The element to insert before. * @param field The field. * @param array true if inserting the array version. * @return The method element. */ protected IJavaElement insertGetter( ICompilationUnit src, IType type, IJavaElement sibling, IField field, boolean array) throws Exception { String propertyType = Signature.getSignatureSimpleName(field.getTypeSignature()); String methodName = StringUtils.capitalize(field.getElementName()); boolean isBoolean = propertyType.equals("boolean"); if(isBoolean){ methodName = "is" + methodName; }else{ methodName = "get" + methodName; } String[] args = null; if(array){ propertyType = Signature.getSignatureSimpleName( Signature.getElementType(field.getTypeSignature())); args = INT_ARG; } IMethod method = type.getMethod(methodName, args); if(!method.exists()){ HashMap<String, Object> values = new HashMap<String, Object>(); values.put("propertyType", propertyType); values.put("name", methodName); values.put("property", field.getElementName()); values.put("array", array ? Boolean.TRUE : Boolean.FALSE); values.put("isBoolean", isBoolean ? Boolean.TRUE : Boolean.FALSE); insertMethod(src, type, method, sibling, GETTER_TEMPLATE, values); } return method; } /** * Insert a setter for the supplied property. * * @param src The src file. * @param type The type to insert into. * @param sibling The element to insert before. * @param field The property. * @param array true if inserting the array version. * @return The method element. */ protected IJavaElement insertSetter( ICompilationUnit src, IType type, IJavaElement sibling, IField field, boolean array) throws Exception { String methodName = "set" + StringUtils.capitalize(field.getElementName()); String propertyType = Signature.getSignatureSimpleName(field.getTypeSignature()); String[] args = new String[]{field.getTypeSignature()}; if(array){ propertyType = Signature.getSignatureSimpleName( Signature.getElementType(field.getTypeSignature())); String elementType = Signature.getElementType(field.getTypeSignature()); args = new String[]{INT_SIG, elementType}; } IMethod method = type.getMethod(methodName, args); if(!method.exists()){ HashMap<String, Object> values = new HashMap<String, Object>(); values.put("name", methodName); values.put("property", field.getElementName()); values.put("propertyType", propertyType); values.put("array", array ? Boolean.TRUE : Boolean.FALSE); values.put("isBoolean", propertyType.equals("boolean") ? Boolean.TRUE : Boolean.FALSE); insertMethod(src, type, method, sibling, SETTER_TEMPLATE, values); } return method; } /** * Inserts a method using the supplied values. * * @param src The src file. * @param type The type to insert into. * @param method The method to be created. * @param sibling The element to insert before. * @param template The template to use. * @param values The values. */ protected void insertMethod( ICompilationUnit src, IType type, IMethod method, IJavaElement sibling, String template, Map<String, Object> values) throws Exception { JavaUtils.loadPreferencesForTemplate( type.getJavaProject().getProject(), getPreferences(), values); TypeInfo superTypeInfo = TypeUtils.getSuperTypeContainingMethod(type, method); if(superTypeInfo != null){ IType superType = superTypeInfo.getType(); values.put("superType", JavaUtils.getCompilationUnitRelativeTypeName(src, superType)); values.put("overrides", superType.isClass() ? Boolean.TRUE : Boolean.FALSE); values.put("implementof", superType.isClass() ? Boolean.FALSE : Boolean.TRUE); values.put("methodSignature", MethodUtils.getMinimalMethodSignature(method, superTypeInfo)); }else{ values.put("superType", null); values.put("overrides", null); values.put("implementof", null); values.put("methodSignature", null); } values.put("isinterface", type.isInterface() ? Boolean.TRUE : Boolean.FALSE); PluginResources resources = (PluginResources) Services.getPluginResources(PluginResources.NAME); String result = TemplateUtils.evaluate(resources, template, values); IMethod inserted = type.createMethod(result, sibling, false, null); // format the inserted method according to the user's preferences Position position = TypeUtils.getPosition(type, inserted); JavaUtils.format( src, CodeFormatter.K_COMPILATION_UNIT, position.getOffset(), position.getLength()); } /** * Determines the sibling to insert relative to for the next property. * * @param type The parent type. * @param fields List of all the fields. * @param field The resolved field. * @param lastSibling The last sibling. * @param methodType The type of the method to be inserted. * * @return The relative sibling to use. */ protected IJavaElement getSibling( IType type, List<IField> fields, IField field, IJavaElement lastSibling, int methodType) throws Exception { // first run through if(lastSibling == null || !lastSibling.exists()){ // first try other methods for the same field. for(int ii = TYPE_GET; ii <= TYPE_SET_INDEX; ii++){ if(ii != methodType){ IMethod method = getBeanMethod(type, field, ii); if(method != null){ if(ii < methodType){ method = MethodUtils.getMethodAfter(type, method); } if(method != null){ return method; }else{ return getFirstInnerType(type); } } } } int index = fields.indexOf(field); // insert before the next property's bean methods, if there are other // properties. if(fields.size() > 1 && (index + 1) < fields.size()){ IMethod method = null; for(int ii = index + 1; method == null && ii < fields.size(); ii++){ IField property = (IField)fields.get(ii); method = getBeanMethod(type, property, false); } if(method != null){ return method; } } // insert after previous property's bean methods, if there are other // properties. if(fields.size() > 1 && index > 0){ IMethod method = null; for(int ii = index - 1; method == null && ii >= 0; ii--){ IField property = (IField)fields.get(ii); method = getBeanMethod(type, property, true); } if(method != null){ method = MethodUtils.getMethodAfter(type, method); if(method != null){ return method; } } } return getFirstInnerType(type); } if(lastSibling != null && lastSibling.exists()){ return lastSibling; } return null; } /** * Attempts to get the method of the supplied type for the specified field. * * @param type The parent type. * @param field The field to retrieve the method for. * @param methodType The method type. * first. * @return The method or null if not round. */ protected IMethod getBeanMethod(IType type, IField field, int methodType) throws Exception { String propertyName = StringUtils.capitalize(field.getElementName()); String name = Signature.getSignatureSimpleName(field.getTypeSignature()); boolean isBoolean = Signature.getSignatureSimpleName( field.getTypeSignature()).equals("boolean"); String signature = null; switch(methodType){ case TYPE_GET: if(isBoolean){ signature = "is" + propertyName + "()"; }else{ signature = " get" + propertyName + "()"; } break; case TYPE_GET_INDEX: if(!isBoolean){ signature = " get" + propertyName + "(int)"; } break; case TYPE_SET: signature = "set" + propertyName + '(' + name + ')'; case TYPE_SET_INDEX: if(!isBoolean){ signature = "set" + propertyName + "(int, " + name + ')'; } } if(signature != null){ IMethod[] methods = type.getMethods(); for(int ii = 0; ii < methods.length; ii++){ String sig = MethodUtils.getMinimalMethodSignature(methods[ii], null); if(sig.equals(signature)){ return methods[ii]; } } } return null; // Weird Eclipse bug: too many calls to IType.getMethod() and createMethod() // calls will fail later. /*switch(methodType){ case TYPE_GET: if(isBoolean){ return type.getMethod("is" + propertyName, null); }else{ return type.getMethod("get" + propertyName, null); } case TYPE_GET_INDEX: if(!isBoolean){ return type.getMethod("get" + propertyName, INT_ARG); } case TYPE_SET: return type.getMethod("set" + propertyName, new String[]{field.getTypeSignature()}); case TYPE_SET_INDEX: if(!isBoolean){ return type.getMethod("set" + propertyName, new String[]{ INT_SIG, Signature.getElementType(field.getTypeSignature())}); } default: return null; }*/ } /** * Gets either the first or last occurring bean method for the supplied field. * * @param field The field to retrieve the method for. * @param last true to return the last declared bean method, false for the * first. * @return The method or null if not round. */ protected IMethod getBeanMethod(IType type, IField field, boolean last) throws Exception { boolean isBoolean = Signature.getSignatureSimpleName( field.getTypeSignature()).equals("boolean"); IMethod result = null; String nextProperty = StringUtils.capitalize(field.getElementName()); // regular getter IMethod method = null; if(isBoolean){ method = type.getMethod("is" + nextProperty, null); }else{ method = type.getMethod("get" + nextProperty, null); } if(method.exists() && !last){ return method; }else if(method.exists()){ result = method; } // index getter if(!isBoolean){ method = type.getMethod("get" + nextProperty, INT_ARG); if(method.exists() && !last){ return method; }else if(method.exists()){ result = method; } } // regular setter method = type.getMethod("set" + nextProperty, new String[]{field.getTypeSignature()}); if(method.exists() && !last){ return method; }else if(method.exists()){ result = method; } // index setter if(!isBoolean){ String elementType = Signature.getElementType(field.getTypeSignature()); method = type.getMethod( "set" + nextProperty, new String[]{INT_SIG, elementType}); if(method.exists()){ result = method; } } return result; } /** * Gets the first non-enum inner type. * * @param type The parent type. * @return The inner type. */ protected IType getFirstInnerType(IType type) throws Exception { // insert before inner classes. IType[] types = type.getTypes(); // find the first non-enum type. for (int ii = 0; ii < types.length; ii++){ if(!types[ii].isEnum()){ return types[ii]; } } return null; } }