1 /* 2 * Copyright (C) 2012 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.caliper.memory; 18 19 import com.google.common.base.Preconditions; 20 21 import java.lang.reflect.Field; 22 import java.util.ArrayDeque; 23 import java.util.Deque; 24 import java.util.Iterator; 25 26 import javax.annotation.Nonnull; 27 import javax.annotation.Nullable; 28 29 /** 30 * A chain of references, which starts at a root object and leads to a 31 * particular value (either an object or a primitive). 32 */ 33 public abstract class Chain { 34 private final Object value; 35 private final Chain parent; 36 37 Chain(Chain parent, Object value) { 38 this.parent = parent; 39 this.value = value; 40 } 41 42 static Chain root(Object value) { 43 return new Chain(null, Preconditions.checkNotNull(value)) { 44 @Override 45 public Class<?> getValueType() { 46 return getValue().getClass(); 47 } 48 }; 49 } 50 51 FieldChain appendField(Field field, Object value) { 52 return new FieldChain(this, Preconditions.checkNotNull(field), value); 53 } 54 55 ArrayIndexChain appendArrayIndex(int arrayIndex, Object value) { 56 return new ArrayIndexChain(this, arrayIndex, value); 57 } 58 59 /** 60 * Returns whether this chain has a parent. This returns false only when 61 * this chain represents the root object itself. 62 */ 63 public boolean hasParent() { 64 return parent != null; 65 } 66 67 /** 68 * Returns the parent chain, from which this chain was created. 69 * @throws IllegalStateException if {@code !hasParent()}, then an 70 */ 71 public @Nonnull Chain getParent() { 72 Preconditions.checkState(parent != null, "This is the root value, it has no parent"); 73 return parent; 74 } 75 76 /** 77 * Returns the value that this chain leads to. If the value is a primitive, 78 * a wrapper object is returned instead. 79 */ 80 public @Nullable Object getValue() { 81 return value; 82 } 83 84 public abstract @Nonnull Class<?> getValueType(); 85 86 /** 87 * Returns whether the connection of the parent chain and this chain is 88 * through a field (of the getParent().getValue().getClass() class). 89 */ 90 public boolean isThroughField() { 91 return false; 92 } 93 94 /** 95 * Returns whether the connection of the parent chain and this chain is 96 * through an array index, i.e. the parent leads to an array, and this 97 * chain leads to an element of that array. 98 */ 99 public boolean isThroughArrayIndex() { 100 return false; 101 } 102 103 /** 104 * Returns whether the value of this chain represents a primitive. 105 */ 106 public boolean isPrimitive() { 107 return getValueType().isPrimitive(); 108 } 109 110 /** 111 * Returns the root object of this chain. 112 */ 113 public @Nonnull Object getRoot() { 114 Chain current = this; 115 while (current.hasParent()) { 116 current = current.getParent(); 117 } 118 return current.getValue(); 119 } 120 121 Deque<Chain> reverse() { 122 Deque<Chain> reverseChain = new ArrayDeque<Chain>(8); 123 Chain current = this; 124 reverseChain.addFirst(current); 125 while (current.hasParent()) { 126 current = current.getParent(); 127 reverseChain.addFirst(current); 128 } 129 return reverseChain; 130 } 131 132 @Override public final String toString() { 133 StringBuilder sb = new StringBuilder(32); 134 135 Iterator<Chain> it = reverse().iterator(); 136 sb.append(it.next().getValue()); 137 while (it.hasNext()) { 138 sb.append("->"); 139 Chain current = it.next(); 140 if (current.isThroughField()) { 141 sb.append(((FieldChain)current).getField().getName()); 142 } else if (current.isThroughArrayIndex()) { 143 sb.append("[").append(((ArrayIndexChain)current).getArrayIndex()).append("]"); 144 } 145 } 146 return sb.toString(); 147 } 148 149 static class FieldChain extends Chain { 150 private final Field field; 151 152 FieldChain(Chain parent, Field referringField, Object value) { 153 super(parent, value); 154 this.field = referringField; 155 } 156 157 @Override 158 public boolean isThroughField() { 159 return true; 160 } 161 162 @Override 163 public boolean isThroughArrayIndex() { 164 return false; 165 } 166 167 @Override 168 public Class<?> getValueType() { 169 return field.getType(); 170 } 171 172 public Field getField() { 173 return field; 174 } 175 } 176 177 static class ArrayIndexChain extends Chain { 178 private final int index; 179 180 ArrayIndexChain(Chain parent, int index, Object value) { 181 super(parent, value); 182 this.index = index; 183 } 184 185 @Override 186 public boolean isThroughField() { 187 return false; 188 } 189 190 @Override 191 public boolean isThroughArrayIndex() { 192 return true; 193 } 194 195 @Override 196 public Class<?> getValueType() { 197 return getParent().getValue().getClass().getComponentType(); 198 } 199 200 public int getArrayIndex() { 201 return index; 202 } 203 } 204 } 205