AttributeLayoutUtils.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 org.apache.commons.compress.harmony.internal;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.compress.harmony.pack200.Pack200Exception;
import org.apache.commons.lang3.IntegerRange;
/**
* Utility methods for {@code attribute_layout} parsing and validation.
*
* @since 1.29.0
*/
public final class AttributeLayoutUtils {
/**
* <pre>
* integral:
* ( unsigned_int | signed_int | bc_index | bc_offset | flag )
* signed_int:
* 'S' uint_type
* any_int:
* ( unsigned_int | signed_int )
* bc_index:
* ( 'P' uint_type | 'PO' uint_type )
* bc_offset:
* 'O' any_int
* flag:
* 'F' uint_type
* uint_type:
* ( 'B' | 'H' | 'I' | 'V' )
* </pre>
*/
private static final Set<String> INTEGRAL_TAGS;
/**
* <pre>
* reference:
* reference_type ( 'N' )? uint_type
* reference_type:
* ( constant_ref | schema_ref | utf8_ref | untyped_ref )
* constant_ref:
* ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' | 'KM' | 'KT' | 'KL' )
* schema_ref:
* ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' | 'RY' | 'RB' | 'RN' )
* utf8_ref:
* 'RU'
* untyped_ref:
* 'RQ'
* uint_type:
* ( 'B' | 'H' | 'I' | 'V' )
* </pre>
*/
private static final Set<String> REFERENCE_TAGS;
/**
* <pre>
* uint_type:
* ( 'B' | 'H' | 'I' | 'V' )
* </pre>
*/
private static final Set<String> UNSIGNED_INT_TAGS;
static {
final Set<String> unsignedIntTags = new HashSet<>();
Collections.addAll(unsignedIntTags,
// unsigned_int
"B", "H", "I", "V");
UNSIGNED_INT_TAGS = Collections.unmodifiableSet(unsignedIntTags);
final Set<String> integralTags = new HashSet<>();
UNSIGNED_INT_TAGS.forEach(tag -> Collections.addAll(integralTags,
// unsigned_int
tag,
// signed_int
"S" + tag,
// bc_index
"P" + tag, "PO" + tag,
// bc_offset
"O" + tag, "OS" + tag,
// flag
"F" + tag));
INTEGRAL_TAGS = Collections.unmodifiableSet(integralTags);
final Set<String> referenceTags = new HashSet<>();
Collections.addAll(referenceTags,
// constant_ref
"KI", "KJ", "KF", "KD", "KS", "KQ", "KM", "KT", "KL",
// schema_ref
"RC", "RS", "RD", "RF", "RM", "RI", "RY", "RB", "RN",
// utf8_ref
"RU",
// untyped_ref
"RQ");
REFERENCE_TAGS = Collections.unmodifiableSet(referenceTags);
}
/**
* Validates that the given tag matches the {@code any_int} layout definition production rule.
*
* @param tag the layout tag to validate
* @return the validated tag
* @throws IllegalArgumentException if the tag is invalid
*/
static String checkAnyIntTag(final String tag) {
if (UNSIGNED_INT_TAGS.contains(tag) || tag.startsWith("S") && UNSIGNED_INT_TAGS.contains(tag.substring(1))) {
return tag;
}
throw new IllegalArgumentException("Invalid any_int layout tag: " + tag);
}
/**
* Validates that the given tag matches the {@code integral} layout definition production rule.
*
* @param tag the layout tag to validate
* @return the validated tag
* @throws IllegalArgumentException if the tag is invalid
*/
public static String checkIntegralTag(final String tag) {
if (INTEGRAL_TAGS.contains(tag)) {
return tag;
}
throw new IllegalArgumentException("Invalid integral layout tag: " + tag);
}
/**
* Validates that the given tag matches the {@code reference} layout definition production rule.
*
* @param tag the layout tag to validate
* @return the validated tag
* @throws IllegalArgumentException if the tag is invalid
*/
public static String checkReferenceTag(final String tag) {
if (tag.length() >= 3) {
final String baseTag = tag.substring(0, 2);
final String uintType = tag.substring(tag.length() - 1);
if (REFERENCE_TAGS.contains(baseTag) && UNSIGNED_INT_TAGS.contains(uintType)
&& (tag.length() == 3 || tag.length() == 4 && tag.charAt(2) == 'N')) {
return tag;
}
}
throw new IllegalArgumentException("Invalid reference layout tag: " + tag);
}
/**
* Validates that the given tag matches the {@code unsigned_int} layout definition production rule.
*
* @param tag the layout tag to validate
* @return the validated tag
* @throws IllegalArgumentException if the tag is invalid
*/
static String checkUnsignedIntTag(final String tag) {
if (UNSIGNED_INT_TAGS.contains(tag)) {
return tag;
}
throw new IllegalArgumentException("Invalid unsigned int layout tag: " + tag);
}
/**
* Reads a {@code attribute_layout} from the stream.
*
* <p>The returned list <strong>may</strong> be empty if the stream is empty.</p>
*
* <pre>
* attribute_layout:
* ( layout_element )* | ( callable )+
* </pre>
*
* @param definition the attribute layout definition body.
* @param factory the factory to create AttributeLayoutElements.
* @param <T> a common type shared by {@code attribute_layout} and {@code callable}.
* @return not empty list of LayoutElements from the body or, if the stream is empty, an empty list.
* @throws Pack200Exception If the layout definition is invalid.
*/
public static <T> List<T> readAttributeLayout(final String definition,
final AttributeLayoutParser.Factory<T> factory) throws Pack200Exception {
final AttributeLayoutParser<T> parser = new AttributeLayoutParser<>(definition, factory);
return parser.readAttributeLayout();
}
/**
* Reads a {@code body} from the stream.
*
* <p>The returned list <strong>may</strong> be empty if the stream is empty.</p>
*
* <pre>
* body:
* ( layout_element )+
* </pre>
*
* @param body the attribute layout definition body.
* @param factory the factory to create LayoutElements.
* @param <T> a common type shared by {@code attribute_layout} and {@code callable}.
* @return not empty list of LayoutElements from the body or, if the stream is empty, an empty list.
* @throws Pack200Exception If the layout definition is invalid.
*/
public static <T> List<T> readBody(final String body, final AttributeLayoutParser.Factory<T> factory) throws Pack200Exception {
final AttributeLayoutParser<T> parser = new AttributeLayoutParser<>(body, factory);
// At depth 0, callables are allowed; increment depth to only allow layout_elements.
parser.incrementDepth();
return parser.readAttributeLayout();
}
/**
* Converts a list of integers to a list of ranges where each range represents a single integer.
*
* @param tags the list of integer tags
* @return a list of ranges representing the tags
*/
public static List<IntegerRange> toRanges(final List<Integer> tags) {
return tags.stream().map(n -> IntegerRange.of(n, n)).collect(Collectors.toList());
}
/**
* Checks if any of the given tag ranges contains the specified tag.
*
* @param tagRanges the list of tag ranges
* @param tag the tag to check
* @return {@code true} if any range contains the tag, {@code false} otherwise
*/
public static boolean unionCaseMatches(final List<IntegerRange> tagRanges, final int tag) {
return tagRanges.stream().anyMatch(r -> r.contains(tag));
}
private AttributeLayoutUtils() {
// Utility class
}
}