| /* |
| * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Tomas Kraus - Initial API and implementation |
| package org.eclipse.persistence.internal.core.queries; |
| |
| import java.util.ArrayDeque; |
| |
| import org.eclipse.persistence.internal.helper.StringHelper; |
| import static org.eclipse.persistence.internal.helper.StringHelper.TAB; |
| import static org.eclipse.persistence.internal.helper.StringHelper.LF; |
| import static org.eclipse.persistence.internal.helper.StringHelper.FF; |
| import static org.eclipse.persistence.internal.helper.StringHelper.CR; |
| import static org.eclipse.persistence.internal.helper.StringHelper.SPACE; |
| |
| // This class have huge performance impact because convert method is used very often. |
| /** |
| * INTERNAL: |
| * AttributeGroup attribute names converter. |
| */ |
| public class CoreAttributeConverter { |
| |
| /** |
| * String containing '.'. |
| */ |
| private static final String DOT = Character.toString(StringHelper.DOT); |
| |
| |
| // Path convert state machine |
| // Splits String around '.' character. Path elements starting or ending |
| // with whitespace or of zero length are considered as illegal. |
| // ,---+ |
| // | x V |
| // x +----+ x |
| // ,---------->| CH |<-----------, |
| // | +----+ | |
| // +-----+ '.' | | ' ' +----+ |
| // | DOT |<--------' `--------->| SP | |
| // +-----+ +----+ |
| // | ' ', '.' +-----+ '.' | |
| // `---------->| ERR |<----------' |
| // +-----+ |
| |
| /** |
| * Path convert state machine internal states. |
| */ |
| private enum ConvertState { |
| /** Initial state or after path elements separator received. */ |
| DOT, |
| /** After space in the middle of path (at least one regular character received in path element). */ |
| SP, |
| /** After regular path character received in path element. */ |
| CH, |
| } |
| |
| // PERF: Whitespace characters set reduced to TAB, LF, FF, CR and SPACE. |
| // Everything is one long and ugly method. |
| /** |
| * INTERNAL: |
| * Splits given <code>nameOrPath[0]</code> argument around <code>'.'</code> character |
| * when <code>nameOrPath.length</code> is equal to 1. Arrays of <code>nameOrPath</code> |
| * containing more than one element are only validated an passed without any changes. |
| * Zero length arrays are considered as invalid. |
| * |
| * @param nameOrPath {@link String} to be split. |
| * @return An array of {@link String}s computed by splitting provided |
| * <code>nameOrPath[0]</code> argument around <code>'.'</code> character. |
| * @throws IllegalArgumentException If <code>nameOrPath</code> argument is <code>null</code> |
| * or any element to be returned after split is <code>null</code>, empty or contains |
| * whitespace at the beginning or end. |
| */ |
| @SuppressWarnings({"fallthrough"}) |
| public static final String[] convert(final String... nameOrPath) |
| throws IllegalArgumentException { |
| if (nameOrPath == null) { |
| throw new IllegalArgumentException("Name or path value is null"); |
| } |
| switch(nameOrPath.length) { |
| // Empty String array. |
| case 0: |
| throw new IllegalArgumentException("Name or path value size is zero"); |
| // Single String: may contain path elements separated by '.'. |
| case 1: |
| final String str = nameOrPath[0]; |
| if (str == null) { |
| throw new IllegalArgumentException("Name or path value is null"); |
| } |
| final int len = str.length(); |
| if (!str.contains(DOT)) { |
| switch(len) { |
| case 0: |
| throw new IllegalArgumentException("Empty name or path value"); |
| case 1: |
| switch(str.charAt(0)) { |
| case TAB: case LF: case FF: case CR: case SPACE: |
| throw new IllegalArgumentException("Name or path value starts with whitespace."); |
| } |
| default: |
| switch(str.charAt(0)) { |
| case TAB: case LF: case FF: case CR: case SPACE: |
| throw new IllegalArgumentException("Name or path value starts with whitespace."); |
| } |
| switch(str.charAt(len - 1)) { |
| case TAB: case LF: case FF: case CR: case SPACE: |
| throw new IllegalArgumentException("Name or path value ends with whitespace."); |
| } |
| } |
| return nameOrPath; |
| } |
| final char[] chars = nameOrPath[0].toCharArray(); |
| // Current character being processed. |
| char c; |
| // Current state machine state. |
| ConvertState s = ConvertState.DOT; |
| // Index of current character in parsed String |
| int index; |
| // Index of 1st path element regular character. |
| int begIndex = 0; |
| // Path elements storage (not needed when there is just one element). |
| ArrayDeque<String> elements = null; |
| for (index = 0; index < len; index++) { |
| c = chars[index]; |
| switch (s) { |
| // Initial state or after path elements separator received. |
| case DOT: |
| switch (c) { |
| // Path elements separator at the beginning or two path element separators |
| // next to each other results in zero length path. |
| case StringHelper.DOT: |
| throw new IllegalArgumentException("Name or path value contains empty path element"); |
| // Whitespace at the beginning. |
| case TAB: case LF: case FF: case CR: case SPACE: |
| throw new IllegalArgumentException("Path element starts with whitespace."); |
| // First regular character. |
| default: |
| s = ConvertState.CH; |
| } |
| break; |
| // After space in the middle of path. |
| case SP: |
| switch (c) { |
| // Path elements separator after whitespace. |
| case StringHelper.DOT: |
| throw new IllegalArgumentException("Path element ends with whitespace."); |
| // Whitespace after whitespace. |
| case TAB: case LF: case FF: case CR: case SPACE: |
| break; |
| // Regular character after whitespace in the middle of path. |
| default: |
| s = ConvertState.CH; |
| } |
| break; |
| // After regular path character received. |
| case CH: |
| switch (c) { |
| // Path element separator after regular path character. |
| case StringHelper.DOT: |
| // Lazy initialization of elements storage. |
| if (elements == null) { |
| elements = new ArrayDeque(4); |
| } |
| // Store finished path element. |
| elements.addLast(new String(chars, begIndex, index - begIndex)); |
| // Next character starts next path element. |
| begIndex = index + 1; |
| s = ConvertState.DOT; |
| break; |
| // Space in the middle of path element. |
| case TAB: case LF: case FF: case CR: case SPACE: |
| s = ConvertState.SP; |
| } |
| } |
| } |
| // Now process end of input attribute. |
| switch (s) { |
| // Last character is path elements separator so there is an empty |
| // path element at the end of string or input string was empty. |
| case DOT: |
| throw new IllegalArgumentException("Empty name or path value or last path element"); |
| // Last character is whitespace. |
| case SP: |
| throw new IllegalArgumentException("Name or path value ends with space"); |
| // Remaining CH as default: Last character is regular character. |
| default: |
| // There was no separator in the name or path value. Original |
| // value is returned. |
| if (elements == null) { |
| return nameOrPath; |
| // Return array of path elements found. Last element is still |
| // in input String. |
| } else { |
| int n = 0, count = elements.size(); |
| String[] paths = new String[count + 1]; |
| while ((paths[n++] = elements.pollFirst()) != null); |
| paths[count] = new String(chars, begIndex, index - begIndex); |
| return paths; |
| } |
| } |
| // Multiple Strings: Validate and pass them as they are. |
| default: |
| String item; |
| for (int i = 0; i < nameOrPath.length; i++) { |
| item = nameOrPath[i]; |
| if (item == null) { |
| throw new IllegalArgumentException("Name or path value at index " |
| + i + " is null"); |
| } |
| final int itemLen = item.length(); |
| // Empty String. |
| if (itemLen == 0) { |
| throw new IllegalArgumentException("Name or path value at index " |
| + i + " is empty String"); |
| // String contains at least one character. |
| } else { |
| switch(item.charAt(0)) { |
| case TAB: case LF: case FF: case CR: case SPACE: |
| throw new IllegalArgumentException("Name or path value at index " |
| + i + " starts with whitespace"); |
| } |
| // String contains more than one character. |
| if (itemLen > 0) { |
| switch(item.charAt(itemLen - 1)) { |
| case TAB: case LF: case FF: case CR: case SPACE: |
| throw new IllegalArgumentException("Name or path value at index " |
| + i + " ends with whitespace"); |
| } |
| } |
| } |
| } |
| return nameOrPath; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This class is just an envelope for static methods so no instances are allowed. |
| */ |
| private CoreAttributeConverter() { |
| throw new UnsupportedOperationException("Instances of CoreAttributeConverter are not allowed"); |
| } |
| |
| } |