/** * 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.impl; import java.util.ArrayList; import java.util.HashMap; 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.logging.Logger; 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.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.formatter.CodeFormatter; /** * Command used to build a tree of methods that have or can be * implemented/overriden by the supplied file according the interfaces/parent * class it implements/extends. * * @author Eric Van Dewoestine */ @Command( name = "java_impl", options = "REQUIRED p project ARG," + "REQUIRED f file ARG," + "OPTIONAL o offset ARG," + "OPTIONAL e encoding ARG," + "OPTIONAL t type ARG," + "OPTIONAL s superType ARG," + "OPTIONAL m methods ARG" ) public class ImplCommand extends AbstractCommand { private static final Logger logger = Logger.getLogger(ImplCommand.class); private static final String TEMPLATE = "method.gst"; /** * {@inheritDoc} */ public Object execute(CommandLine commandLine) throws Exception { String project = commandLine.getValue(Options.PROJECT_OPTION); String file = commandLine.getValue(Options.FILE_OPTION); String superTypeName = commandLine.getValue(Options.SUPERTYPE_OPTION); ICompilationUnit src = JavaUtils.getCompilationUnit(project, file); IType type = null; if(superTypeName != null){ type = src.getJavaProject().findType( commandLine.getValue(Options.TYPE_OPTION).replace('$', '.')); int index = superTypeName.indexOf('<'); String[] typeArgs = null; if (index != -1){ String superTypeArgs = superTypeName.substring(index); superTypeName = superTypeName.substring(0, index); ArrayList<String> args = new ArrayList<String>(); char[] argsArray = superTypeArgs .substring(1, superTypeArgs.length() - 1).toCharArray(); int typeDepth = 0; StringBuffer arg = new StringBuffer(); for (char c : argsArray){ if (c == '<'){ typeDepth++; arg.append(c); }else if (c == '>'){ typeDepth--; arg.append(c); }else if (c == ',' && typeDepth == 0){ args.add(arg.toString()); arg = new StringBuffer(); }else{ arg.append(c); } } if (arg.length() > 0){ args.add(arg.toString()); } typeArgs = args.toArray(new String[args.size()]); } IType superType = type.getJavaProject().findType(superTypeName); ITypeParameter[] params = superType.getTypeParameters(); String[] typeParams = new String[params.length]; for (int ii = 0; ii < params.length; ii++){ typeParams[ii] = params[ii].getElementName(); } TypeInfo superTypeInfo = new TypeInfo(superType, typeParams, typeArgs); String methodsOption = commandLine.getValue(Options.METHOD_OPTION); String[] methods = null; if(methodsOption != null){ methods = StringUtils.splitByWholeSeparator(methodsOption, ",,"); }else{ methods = getUnimplementedMethods(type, superTypeInfo); } for(int ii = 0; ii < methods.length; ii++){ executeInsertMethod(commandLine, src, type, superTypeInfo, methods[ii]); } } if(type == null){ int offset = commandLine.getIntValue(Options.OFFSET_OPTION); if (offset != -1){ offset = getOffset(commandLine); } type = TypeUtils.getType(src, offset); } return executeGetMethods(commandLine, type); } /** * Gets all the methods of the supertypes for the supplied type. * * @param commandLine The original command line. * @param type The type. * @return ImplResult */ protected ImplResult executeGetMethods(CommandLine commandLine, IType type) throws Exception { ArrayList<ImplType> results = new ArrayList<ImplType>(); if(isValidType(type)){ Map<String, IMethod> implementedMethods = getImplementedMethods(type); TypeInfo[] types = getSuperTypes(commandLine, type); for(TypeInfo info : types){ IType superType = info.getType(); ImplType implType = new ImplType(); implType.setPackage(superType.getPackageFragment().getElementName()); implType.setExists(superType.exists()); if(superType.exists()){ implType.setSignature(TypeUtils.getTypeSignature(info)); implType.setMethods(getMethods(type, implementedMethods, info)); }else{ implType.setSignature(superType.getElementName()); } results.add(implType); } }else{ throw new IllegalArgumentException( Services.getMessage("type.not.a.class", type.getFullyQualifiedName())); } return new ImplResult(type.getFullyQualifiedName(), results); } /** * Inserts a stub for the supplied method or for all methods in the supplied * super type if no method supplied. * * @param commandLine The original command line. * @param src The compilation unit. * @param type The type to insert the method(s) into. * @param superTypeInfo The super type to insert methods from. * @param methodName The super type to insert methods from. * * @return The Position where the method(s) where inserted. */ protected Object executeInsertMethod( CommandLine commandLine, ICompilationUnit src, IType type, TypeInfo superTypeInfo, String methodName) throws Exception { IType superType = superTypeInfo.getType(); IMethod[] methods = superType.getMethods(); Map<String, IMethod> implementedMethods = getImplementedMethods(type); IMethod method = null; for(int ii = 0; ii < methods.length; ii++){ String sig = MethodUtils.getMinimalMethodSignature(methods[ii], superTypeInfo); if(sig.equals(methodName)){ method = methods[ii]; break; } } if(method == null){ logger.warn(Services.getMessage("method.not.found", superType.getFullyQualifiedName(), methodName)); return null; } if(getImplemented(type, implementedMethods, superTypeInfo, method) != null){ logger.warn(Services.getMessage("method.already.implemented", type.getFullyQualifiedName(), superType.getFullyQualifiedName(), methodName )); return null; } IJavaElement sibling = getSibling(type, implementedMethods, superTypeInfo, methods, method); insertMethod(commandLine, src, type, superTypeInfo, method, sibling); return null; } /** * Gets the names of the unimplemented methods from the super type. * * @param type The type to add the methods to. * @param superTypeInfo The super type to add methods from. * @return Array of minimal method signatures. */ protected String[] getUnimplementedMethods(IType type, TypeInfo superTypeInfo) throws Exception { ArrayList<String> names = new ArrayList<String>(); IType superType = superTypeInfo.getType(); IMethod[] methods = superType.getMethods(); Map<String, IMethod> implementedMethods = getImplementedMethods(type); for(int ii = 0; ii < methods.length; ii++){ int flags = methods[ii].getFlags(); IMethod implemented = getImplemented(type, implementedMethods, superTypeInfo, methods[ii]); if (!Flags.isStatic(flags) && !Flags.isFinal(flags) && !Flags.isPrivate(flags) && !methods[ii].isConstructor() && implemented == null) { String sig = MethodUtils.getMinimalMethodSignature(methods[ii], superTypeInfo); names.add(sig); } } return (String[])names.toArray(new String[names.size()]); } /** * Gets all the super types for the supplied type. * * @param commandLine The original command line. * @param type The type. * @return The super types. */ protected TypeInfo[] getSuperTypes(CommandLine commandLine, IType type) throws Exception { return TypeUtils.getSuperTypes(type, true); } /** * Gets a map of minimal method signatures and methods implemented by the * supplied type. * * @param type The type. * @return Map of minimal method signatures and the corresponding methods. */ protected Map<String, IMethod> getImplementedMethods(IType type) throws Exception { TypeInfo typeInfo = new TypeInfo(type, null, null); HashMap<String, IMethod> implementedMethods = new HashMap<String, IMethod>(); IMethod[] methods = type.getMethods(); for(int ii = 0; ii < methods.length; ii++){ int flags = methods[ii].getFlags(); if (!Flags.isStatic(flags) && !Flags.isFinal(flags) && !Flags.isPrivate(flags)) { implementedMethods.put( MethodUtils.getMinimalMethodSignature(methods[ii], typeInfo), methods[ii]); } } return implementedMethods; } /** * Inserts the supplied method into the specified source type. * * @param commandLine The original command line. * @param src The compilation unit. * @param type The type to insert the method into. * @param superTypeInfo The super type the method is defined in. * @param method The method to insert. * @param sibling The element to insert the new method before, or null to * append to the end. * @return The position the method was inserted at. */ protected Position insertMethod( CommandLine commandLine, ICompilationUnit src, IType type, TypeInfo superTypeInfo, IMethod method, IJavaElement sibling) throws Exception { HashMap<String, Object> values = new HashMap<String, Object>(); JavaUtils.loadPreferencesForTemplate( type.getJavaProject().getProject(), getPreferences(), values); if(!method.isConstructor()){ String rtype = Signature.getSignatureSimpleName(method.getReturnType()); values.put("constructor", Boolean.FALSE); values.put("name", method.getElementName()); values.put("returnType", TypeUtils.replaceTypeParams(rtype, superTypeInfo)); values.put("methodBody", null); }else{ values.put("constructor", Boolean.TRUE); values.put("name", type.getElementName()); StringBuffer buffer = new StringBuffer("super("); String[] paramNames = method.getParameterNames(); for(int ii = 0; ii < paramNames.length; ii++){ if(ii != 0){ buffer.append(", "); } buffer.append(paramNames[ii]); } buffer.append(");"); values.put("methodBody", buffer.toString()); values.put("returnType", null); } IType superType = superTypeInfo.getType(); if(superType.isInterface()){ values.put("modifier", "public"); }else{ values.put("modifier", Flags.isPublic(method.getFlags()) ? "public" : "protected"); } values.put("superType", JavaUtils.getCompilationUnitRelativeTypeName(src, superType)); values.put("params", MethodUtils.getMethodParameters(method, superTypeInfo, true)); 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)); String thrown = MethodUtils.getMethodThrows(method); values.put("throwsType", thrown != null ? thrown : null); values.put("delegate", Boolean.FALSE); PluginResources resources = (PluginResources) Services.getPluginResources(PluginResources.NAME); String result = TemplateUtils.evaluate(resources, TEMPLATE, values); Position position = TypeUtils.getPosition(type, type.createMethod(result, sibling, false, null)); JavaUtils.format( src, CodeFormatter.K_COMPILATION_UNIT, position.getOffset(), position.getLength()); return position; } /** * Gets the methods from the super type. * * @param type The type to be modified. * @param baseMethods The base methods from the base type. * @param superTypeInfo The super type info. * * @return Array of methods. */ protected ImplMethod[] getMethods( IType type, Map<String, IMethod> baseMethods, TypeInfo superTypeInfo) throws Exception { ArrayList<ImplMethod> results = new ArrayList<ImplMethod>(); IMethod[] methods = superTypeInfo.getType().getMethods(); for(int ii = 0; ii < methods.length; ii++){ IMethod method = methods[ii]; if(isValidMethod(method)){ String signature = MethodUtils.getMethodSignature(method, superTypeInfo); ImplMethod implMethod = new ImplMethod(); implMethod.setSignature(signature); implMethod.setImplemented( getImplemented(type, baseMethods, superTypeInfo, method) != null); results.add(implMethod); } } return (ImplMethod[])results.toArray(new ImplMethod[results.size()]); } /** * Determine if the supplied method should be included in list of * overridable / implmentable methods. * * @param method The method. * @return true is should be included, false otherwise. */ protected boolean isValidMethod(IMethod method) throws Exception { int flags = method.getFlags(); return (!Flags.isStatic(flags) && !Flags.isFinal(flags) && !Flags.isPrivate(flags)); } /** * Determines if the supplied type is valid for overriding / implementing * methods. * * @param type The type. * @return true if valid, false otherwise. */ protected boolean isValidType(IType type) throws Exception { return type.isClass(); } /** * Gets the implemented version of the supplied method. * * @param type The type to be modified. * @param baseMethods The list of methods defined in the base. * @param method The method to test for. * @return The implemented method or null if none. */ protected IMethod getImplemented( IType type, Map<String, IMethod> baseMethods, TypeInfo superTypeInfo, IMethod method) throws Exception { String signature = MethodUtils.getMinimalMethodSignature(method, superTypeInfo); if(method.isConstructor()){ signature = signature.replaceFirst( method.getDeclaringType().getElementName(), type.getElementName()); } return baseMethods.get(signature); } /** * Gets the sibling to insert before. * * @param type The type to insert into. * @param baseMethods The currently implemented methods. * @param superTypeInfo The super type info. * @param methods The super types methods. * @param method The method to be added. * @return The sibling, or null if none. */ protected IJavaElement getSibling( IType type, Map<String, IMethod> baseMethods, TypeInfo superTypeInfo, IMethod[] methods, IMethod method) throws Exception { int index = -1; int implementedIndex = -1; IJavaElement sibling = null; // find the nearest implemented method for (int ii = 0; ii < methods.length; ii++){ if(methods[ii].equals(method)){ index = ii; }else{ IMethod implemented = getImplemented(type, baseMethods, superTypeInfo, methods[ii]); if(implemented != null){ implementedIndex = ii; sibling = implemented; if(index != -1){ break; } } } } if(implementedIndex < index && sibling != null){ // get the method after the sibling. sibling = MethodUtils.getMethodAfter(type, (IMethod)sibling); } // no sibling, get first non enum type. if(sibling == null){ IType[] types = type.getTypes(); if(types.length > 0){ // find the first non-enum type. for (int ii = 0; ii < types.length; ii++){ if(!types[ii].isEnum()){ return types[ii]; } } } } return sibling; } }