blob: 9273b104c9766f4da89a1e613100f4dc5bec2b7c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.checks;
import static com.android.SdkConstants.ATTR_VALUE;
import static com.android.tools.lint.checks.AnnotationDetector.ATTR_MAX;
import static com.android.tools.lint.checks.AnnotationDetector.ATTR_MIN;
import static com.android.tools.lint.checks.AnnotationDetector.ATTR_MULTIPLE;
import static com.android.tools.lint.checks.AnnotationDetector.SIZE_ANNOTATION;
import static com.android.tools.lint.detector.api.UastLintUtils.getAnnotationLongValue;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.google.common.annotations.VisibleForTesting;
import org.jetbrains.uast.UAnnotation;
import org.jetbrains.uast.UExpression;
class SizeConstraint extends RangeConstraint {
final long exact;
final long min;
final long max;
final long multiple;
@NonNull
public static SizeConstraint create(@NonNull UAnnotation annotation) {
assert SIZE_ANNOTATION.isEquals(annotation.getQualifiedName());
long exact = getAnnotationLongValue(annotation, ATTR_VALUE, -1);
long min = getAnnotationLongValue(annotation, ATTR_MIN, Long.MIN_VALUE);
long max = getAnnotationLongValue(annotation, ATTR_MAX, Long.MAX_VALUE);
long multiple = getAnnotationLongValue(annotation, ATTR_MULTIPLE, 1);
return new SizeConstraint(exact, min, max, multiple);
}
@VisibleForTesting
@NonNull
static SizeConstraint exactly(long value) {
return new SizeConstraint(value, Long.MIN_VALUE, Long.MAX_VALUE, 1);
}
@VisibleForTesting
@NonNull
static SizeConstraint atLeast(long value) {
return new SizeConstraint(-1, value, Long.MAX_VALUE, 1);
}
@VisibleForTesting
@NonNull
static SizeConstraint atMost(long value) {
return new SizeConstraint(-1, Long.MIN_VALUE, value, 1);
}
@VisibleForTesting
@NonNull
static SizeConstraint range(long from, long to) {
return new SizeConstraint(-1, from, to, 1);
}
@VisibleForTesting
@NonNull
static SizeConstraint multiple(int multiple) {
return new SizeConstraint(-1, Long.MIN_VALUE, Long.MAX_VALUE, multiple);
}
@VisibleForTesting
@NonNull
static SizeConstraint rangeWithMultiple(long from, long to, int multiple) {
return new SizeConstraint(-1, from, to, multiple);
}
@VisibleForTesting
@NonNull
static SizeConstraint minWithMultiple(long from, int multiple) {
return new SizeConstraint(-1, from, Long.MAX_VALUE, multiple);
}
private SizeConstraint(long exact, long min, long max, long multiple) {
this.exact = exact;
this.min = min;
this.max = max;
this.multiple = multiple;
}
@Override
public String toString() {
return describe(null, null, null);
}
public boolean isValid(long actual) {
if (exact != -1) {
if (exact != actual) {
return false;
}
} else if (actual < min || actual > max || actual % multiple != 0) {
return false;
}
return true;
}
@NonNull
public String describe() {
return describe(null, null, null);
}
@NonNull
public String describe(long argument) {
return describe(null, null, argument);
}
@NonNull
public String describe(
@Nullable UExpression argument, @Nullable String unit, @Nullable Long actualValue) {
if (unit == null) {
if (argument != null
&& argument.getExpressionType() != null
&& argument.getExpressionType().getCanonicalText().equals(JAVA_LANG_STRING)) {
unit = "Length";
} else {
unit = "Size";
}
}
if (actualValue != null && !isValid(actualValue)) {
long actual = actualValue;
if (exact != -1) {
if (exact != actual) {
return String.format("Expected %1$s %2$d (was %3$d)", unit, exact, actual);
}
} else if (actual < min || actual > max) {
StringBuilder sb = new StringBuilder(20);
if (actual < min) {
sb.append("Expected ").append(unit).append(" \u2265 ");
sb.append(Long.toString(min));
} else {
assert actual > max;
sb.append("Expected ").append(unit).append(" \u2264 ");
sb.append(Long.toString(max));
}
sb.append(" (was ").append(actual).append(')');
return sb.toString();
} else if (actual % multiple != 0) {
return String.format(
"Expected %1$s to be a multiple of %2$d (was %3$d "
+ "and should be either %4$d or %5$d)",
unit,
multiple,
actual,
(actual / multiple) * multiple,
(actual / multiple + 1) * multiple);
}
}
StringBuilder sb = new StringBuilder(20);
sb.append(unit);
sb.append(" must be");
if (exact != -1) {
sb.append(" exactly ");
sb.append(Long.toString(exact));
return sb.toString();
}
boolean continued = true;
if (min != Long.MIN_VALUE && max != Long.MAX_VALUE) {
sb.append(" at least ");
sb.append(Long.toString(min));
sb.append(" and at most ");
sb.append(Long.toString(max));
} else if (min != Long.MIN_VALUE) {
sb.append(" at least ");
sb.append(Long.toString(min));
} else if (max != Long.MAX_VALUE) {
sb.append(" at most ");
sb.append(Long.toString(max));
} else {
continued = false;
}
if (multiple != 1) {
if (continued) {
sb.append(" and");
}
sb.append(" a multiple of ");
sb.append(Long.toString(multiple));
}
if (actualValue != null) {
sb.append(" (was ").append(actualValue).append(')');
}
return sb.toString();
}
@Nullable
@Override
public Boolean contains(@NonNull RangeConstraint other) {
if (other instanceof SizeConstraint) {
SizeConstraint otherRange = (SizeConstraint) other;
if (exact != -1 && otherRange.exact != -1) {
return exact == otherRange.exact;
}
if (multiple != 1) {
if (otherRange.exact != -1) {
if (otherRange.exact % multiple != 0) {
return false;
}
} else if (otherRange.multiple % multiple != 0) {
return false;
}
}
if (otherRange.exact != -1) {
return otherRange.exact >= min && otherRange.exact <= max;
}
return otherRange.min >= min && otherRange.max <= max;
}
return null;
}
}