1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.googlejavaformat.java; 16 17 import com.google.common.collect.ImmutableList; 18 import com.sun.source.tree.AnnotatedTypeTree; 19 import com.sun.source.tree.AnnotationTree; 20 import com.sun.source.tree.ArrayTypeTree; 21 import com.sun.source.tree.Tree; 22 import com.sun.tools.javac.tree.JCTree; 23 import java.util.ArrayDeque; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.Deque; 27 import java.util.List; 28 29 /** 30 * Utilities for working with array dimensions. 31 * 32 * <p>javac's parser does not preserve concrete syntax for mixed-notation arrays, so we have to 33 * re-lex the input to extra it. 34 * 35 * <p>For example, {@code int [] a;} cannot be distinguished from {@code int [] a [];} in the AST. 36 */ 37 class DimensionHelpers { 38 39 /** The array dimension specifiers (including any type annotations) associated with a type. */ 40 static class TypeWithDims { 41 final Tree node; 42 final ImmutableList<List<AnnotationTree>> dims; 43 TypeWithDims(Tree node, ImmutableList<List<AnnotationTree>> dims)44 public TypeWithDims(Tree node, ImmutableList<List<AnnotationTree>> dims) { 45 this.node = node; 46 this.dims = dims; 47 } 48 } 49 50 enum SortedDims { 51 YES, 52 NO 53 } 54 55 /** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */ extractDims(Tree node, SortedDims sorted)56 static TypeWithDims extractDims(Tree node, SortedDims sorted) { 57 Deque<List<AnnotationTree>> builder = new ArrayDeque<>(); 58 node = extractDims(builder, node); 59 Iterable<List<AnnotationTree>> dims; 60 if (sorted == SortedDims.YES) { 61 dims = reorderBySourcePosition(builder); 62 } else { 63 dims = builder; 64 } 65 return new TypeWithDims(node, ImmutableList.copyOf(dims)); 66 } 67 68 /** 69 * Rotate the list of dimension specifiers until all dimensions with type annotations appear in 70 * source order. 71 * 72 * <p>javac reorders dimension specifiers in method declarations with mixed-array notation, which 73 * means that any type annotations don't appear in source order. 74 * 75 * <p>For example, the type of {@code int @A [] f() @B [] {}} is parsed as {@code @B [] @A []}. 76 * 77 * <p>This doesn't handle cases with un-annotated dimension specifiers, so the formatting logic 78 * checks the token stream to figure out which side of the method name they appear on. 79 */ reorderBySourcePosition( Deque<List<AnnotationTree>> dims)80 private static Iterable<List<AnnotationTree>> reorderBySourcePosition( 81 Deque<List<AnnotationTree>> dims) { 82 int lastAnnotation = -1; 83 int lastPos = -1; 84 int idx = 0; 85 for (List<AnnotationTree> dim : dims) { 86 if (!dim.isEmpty()) { 87 int pos = ((JCTree) dim.get(0)).getStartPosition(); 88 if (pos < lastPos) { 89 List<List<AnnotationTree>> list = new ArrayList<>(dims); 90 Collections.rotate(list, -(lastAnnotation + 1)); 91 return list; 92 } 93 lastPos = pos; 94 lastAnnotation = idx; 95 } 96 idx++; 97 } 98 return dims; 99 } 100 101 /** 102 * Accumulates a flattened list of array dimensions specifiers with type annotations, and returns 103 * the base type. 104 * 105 * <p>Given {@code int @A @B [][] @C []}, adds {@code [[@A, @B], [@C]]} to dims and returns {@code 106 * int}. 107 */ extractDims(Deque<List<AnnotationTree>> dims, Tree node)108 private static Tree extractDims(Deque<List<AnnotationTree>> dims, Tree node) { 109 switch (node.getKind()) { 110 case ARRAY_TYPE: 111 return extractDims(dims, ((ArrayTypeTree) node).getType()); 112 case ANNOTATED_TYPE: 113 AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node; 114 if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE) { 115 return node; 116 } 117 node = extractDims(dims, annotatedTypeTree.getUnderlyingType()); 118 dims.addFirst(ImmutableList.copyOf(annotatedTypeTree.getAnnotations())); 119 return node; 120 default: 121 return node; 122 } 123 } 124 } 125