001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package eu.cqse.check.framework.util; 018 019import static eu.cqse.check.framework.scanner.ETokenType.BOOLEAN; 020import static eu.cqse.check.framework.scanner.ETokenType.BYTE; 021import static eu.cqse.check.framework.scanner.ETokenType.CHAR; 022import static eu.cqse.check.framework.scanner.ETokenType.COLON; 023import static eu.cqse.check.framework.scanner.ETokenType.DOT; 024import static eu.cqse.check.framework.scanner.ETokenType.DOUBLE; 025import static eu.cqse.check.framework.scanner.ETokenType.DOUBLE_COLON; 026import static eu.cqse.check.framework.scanner.ETokenType.FLOAT; 027import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER; 028import static eu.cqse.check.framework.scanner.ETokenType.INT; 029import static eu.cqse.check.framework.scanner.ETokenType.LBRACE; 030import static eu.cqse.check.framework.scanner.ETokenType.LONG; 031import static eu.cqse.check.framework.scanner.ETokenType.RBRACE; 032import static eu.cqse.check.framework.scanner.ETokenType.SHORT; 033import static eu.cqse.check.framework.scanner.ETokenType.STATIC; 034import static eu.cqse.check.framework.scanner.ETokenType.VOID; 035 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collections; 039import java.util.EnumSet; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Set; 043 044import org.conqat.lib.commons.assertion.CCSMAssert; 045import org.conqat.lib.commons.collections.CollectionUtils; 046import org.conqat.lib.commons.collections.UnmodifiableList; 047import org.conqat.lib.commons.string.StringUtils; 048 049import eu.cqse.check.framework.scanner.ELanguage; 050import eu.cqse.check.framework.scanner.ETokenType; 051import eu.cqse.check.framework.scanner.IToken; 052import eu.cqse.check.framework.shallowparser.SubTypeNames; 053import eu.cqse.check.framework.shallowparser.TokenStreamTextUtils; 054import eu.cqse.check.framework.shallowparser.TokenStreamUtils; 055import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType; 056import eu.cqse.check.framework.shallowparser.framework.ShallowEntity; 057import eu.cqse.check.framework.shallowparser.util.ShallowParsingUtils; 058import eu.cqse.check.framework.util.variable.CLikeVariableUseExtractor; 059 060/** 061 * Language feature parser for Java. 062 */ 063public class JavaLanguageFeatureParser extends CLikeLanguageFeatureParserBase { 064 065 /** All token types that can be used to specify a non primitive type. */ 066 public static final EnumSet<ETokenType> ADDITIONAL_TYPE_TOKENS = EnumSet.of(IDENTIFIER); 067 068 /** All token types that can be used to specify a primitive type. */ 069 public static final EnumSet<ETokenType> PRIMITIVE_TYPE_TOKENS = EnumSet.of(BOOLEAN, CHAR, BYTE, SHORT, INT, LONG, 070 FLOAT, DOUBLE, VOID); 071 072 /** All token types that can be used as identifier. */ 073 public static final EnumSet<ETokenType> VALID_IDENTIFIERS = EnumSet.of(IDENTIFIER); 074 075 /** Names of classes that count as generic exceptions. */ 076 private static final Set<String> GENERIC_EXCEPTION_NAMES = new HashSet<>(); 077 078 static { 079 // Exception classes that count as generic exceptions 080 Set<Class<? extends Throwable>> genericExceptions = CollectionUtils.asHashSet(RuntimeException.class, 081 Error.class, Throwable.class, Exception.class); 082 for (Class<? extends Throwable> clazz : genericExceptions) { 083 GENERIC_EXCEPTION_NAMES.add(clazz.getSimpleName()); 084 GENERIC_EXCEPTION_NAMES.add(clazz.getName()); 085 } 086 } 087 088 /** Constructor. */ 089 public JavaLanguageFeatureParser() { 090 super(ELanguage.JAVA, VALID_IDENTIFIERS, PRIMITIVE_TYPE_TOKENS, ADDITIONAL_TYPE_TOKENS, DOT, ".", 091 new CLikeVariableUseExtractor(EnumSet.of(DOT, DOUBLE_COLON), VALID_IDENTIFIERS)); 092 } 093 094 /** 095 * Returns the imported package name from a given "import" entity. Static 096 * imports are ignored. 097 */ 098 @Override 099 public String getImportName(ShallowEntity entity) { 100 CCSMAssert.isTrue(isImport(entity), "entity.getType() must be equal to EShallowEntityType.META and " 101 + "entity.getSubtype() must be equal to SubTypeNames.IMPORT or SubTypeNames.STATIC_IMPORT"); 102 103 List<IToken> tokens = entity.ownStartTokens(); 104 if (TokenStreamUtils.firstTokenOfType(tokens, STATIC) != TokenStreamUtils.NOT_FOUND) { 105 return null; 106 } 107 108 String importName = entity.getName(); 109 return StringUtils.removeLastPart(importName, '.'); 110 } 111 112 /** 113 * Returns whether the given entity is an import (including static imports). 114 */ 115 @Override 116 public boolean isImport(ShallowEntity entity) { 117 return entity.getType().equals(EShallowEntityType.META) && (entity.getSubtype().equals(SubTypeNames.IMPORT) 118 || entity.getSubtype().equals(SubTypeNames.STATIC_IMPORT)); 119 } 120 121 /** Returns whether the given entity is a constant. */ 122 public boolean isConstant(ShallowEntity entity) { 123 if (entity.getType() != EShallowEntityType.ATTRIBUTE) { 124 return false; 125 } 126 127 // We require "static final", as "final" alone only indicates 128 // immutable field but not "classic" constants 129 if (TokenStreamUtils.containsAll(entity.ownStartTokens(), ETokenType.FINAL, ETokenType.STATIC)) { 130 return true; 131 } 132 133 // If the attribute is part of an interface, it is always constant 134 ShallowEntity parent = entity.getParent(); 135 return parent != null && parent.getType().equals(EShallowEntityType.TYPE) 136 && (parent.getSubtype().equals(SubTypeNames.INTERFACE) 137 || parent.getSubtype().equals(SubTypeNames.ANNOTATION_INTERFACE)); 138 } 139 140 /** Returns whether the given entity is a for each loop. */ 141 public boolean isForEachLoop(ShallowEntity entity) { 142 if (entity.getType().equals(EShallowEntityType.STATEMENT) && entity.getSubtype().equals(SubTypeNames.FOR)) { 143 return TokenStreamUtils.findFirstTopLevel(entity.ownStartTokens(), COLON, Arrays.asList(LBRACE), 144 Arrays.asList(RBRACE)) != TokenStreamUtils.NOT_FOUND; 145 } 146 return false; 147 } 148 149 /** Returns whether the given entity is an annotation. */ 150 public boolean isAnnotation(ShallowEntity entity) { 151 return entity.getType() == EShallowEntityType.META && SubTypeNames.ANNOTATION.equals(entity.getSubtype()); 152 } 153 154 /** Returns all annotation entities that annotate the given entity. */ 155 public List<ShallowEntity> getAnnotations(ShallowEntity entity) { 156 return getAnnotations(entity, false); 157 } 158 159 /** Returns all annotation entities that annotate the given entity. */ 160 public List<ShallowEntity> getAnnotations(ShallowEntity entity, boolean includeParentEntities) { 161 ShallowEntity parent = entity.getParent(); 162 if (parent == null) { 163 return CollectionUtils.emptyList(); 164 } 165 166 Set<ShallowEntity> annotations = new HashSet<>(); 167 168 UnmodifiableList<ShallowEntity> entities = parent.getChildren(); 169 for (int i = entities.indexOf(entity) - 1; i >= 0; i--) { 170 ShallowEntity previousEntity = entities.get(i); 171 if (!isAnnotation(previousEntity)) { 172 break; 173 } 174 annotations.add(previousEntity); 175 } 176 177 if (includeParentEntities) { 178 annotations.addAll(getAnnotations(entity.getParent(), true)); 179 } 180 181 return new ArrayList<>(annotations); 182 } 183 184 /** 185 * Returns all token lists representing thrown types of a method in the order of 186 * declaration. 187 */ 188 public List<List<IToken>> getThrownTypesTokens(ShallowEntity methodEntity) { 189 CCSMAssert.isTrue(methodEntity.getType().equals(EShallowEntityType.METHOD), 190 "Only methods can throw exceptions"); 191 192 List<IToken> tokens = methodEntity.ownStartTokens(); 193 List<IToken> thrownClauseTokens = TokenStreamUtils.tokensBetween(tokens, EnumSet.of(ETokenType.THROWS), 194 EnumSet.of(ETokenType.LBRACE, ETokenType.SEMICOLON)); 195 196 if (thrownClauseTokens.isEmpty()) { 197 return CollectionUtils.emptyList(); 198 } 199 200 return TokenStreamUtils.split(thrownClauseTokens, ETokenType.COMMA); 201 } 202 203 /** 204 * Returns the token of the throws keyword of a given method. If no throws 205 * keyword is found null is returned. 206 */ 207 public IToken getThrowsToken(ShallowEntity methodEntity) { 208 CCSMAssert.isTrue(methodEntity.getType().equals(EShallowEntityType.METHOD), 209 "Only methods can throw exceptions"); 210 211 List<IToken> tokens = methodEntity.ownStartTokens(); 212 int throwsTokenIndex = TokenStreamUtils.firstTokenOfType(tokens, ETokenType.THROWS); 213 214 if (throwsTokenIndex == TokenStreamUtils.NOT_FOUND) { 215 return null; 216 } 217 218 return tokens.get(throwsTokenIndex); 219 } 220 221 /** Returns if the entity has at least one annotation. */ 222 public boolean isAnnotated(ShallowEntity entity) { 223 return !getAnnotations(entity).isEmpty(); 224 } 225 226 /** 227 * Get the detailed return type of a method. The detailed return type includes 228 * the type name and array and generic declarations. 229 */ 230 public String getDetailedReturnType(ShallowEntity entity) { 231 CCSMAssert.isTrue(entity.getType() == EShallowEntityType.METHOD, "May only be applied to methods!"); 232 CCSMAssert.isFalse(ShallowParsingUtils.isLambdaMethod(entity), "May not be applied to lambda methods!"); 233 234 List<IToken> tokens = entity.ownStartTokens(); 235 int nameIndex = getMethodNameIndex(entity, tokens); 236 List<IToken> typeTokens = getDetailedReturnTypeTokens(tokens, nameIndex); 237 return TokenStreamTextUtils.concatTokenTexts(typeTokens); 238 } 239 240 /** 241 * Returns the index to the name of the given entity within the given tokens. 242 */ 243 private static int getMethodNameIndex(ShallowEntity entity, List<IToken> tokens) { 244 int nameIndex = 0; 245 for (IToken token : tokens) { 246 if (token.getText().equals(entity.getName())) { 247 nameIndex = tokens.indexOf(token); 248 } 249 } 250 return nameIndex; 251 } 252 253 /** 254 * Returns the detailled return type tokens including arrays and generics from 255 * the given method tokens. The nameIndex must point to the name token of the 256 * method. 257 */ 258 private static List<IToken> getDetailedReturnTypeTokens(List<IToken> tokens, int nameIndex) { 259 int startIndex = nameIndex - 1; 260 while (startIndex >= 2) { 261 ETokenType startType = tokens.get(startIndex).getType(); 262 if (startType == ETokenType.RBRACK) { 263 startIndex -= 2; 264 } else if (startType == ETokenType.GT) { 265 startIndex = TokenStreamUtils.findMatchingOpeningToken(tokens, startIndex - 1, ETokenType.LT, 266 ETokenType.GT) - 1; 267 } else if (startType == ETokenType.IDENTIFIER && startIndex > 0 268 && tokens.get(startIndex - 1).getType() == ETokenType.DOT) { 269 startIndex -= 2; 270 } else { 271 break; 272 } 273 } 274 return tokens.subList(startIndex, nameIndex); 275 } 276 277 /** 278 * Determines whether the given class name represents a generic exception. 279 */ 280 public boolean isGenericExceptionClass(String className) { 281 return GENERIC_EXCEPTION_NAMES.contains(className); 282 } 283 284 @Override 285 protected List<IToken> filterGenericTokens(List<IToken> genericTokens) { 286 if (genericTokens.isEmpty()) { 287 return genericTokens; 288 } 289 // only return first token (i.e. skip "extends" part) 290 return Collections.singletonList(genericTokens.get(0)); 291 } 292}