package io.oasp.module.basic.common.api.reflect;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* This class represents a {@link Package} following the
* <a href="https://github.com/oasp/oasp4j/wiki/coding-conventions#packages">OASP coding convetion</a>. <br>
* After parsing a {@link Package} as {@link OaspPackage} you can get individual parts/segments such as
* {@link #getComponent() compoent}, {@link #getLayer() layer}, {@link #getScope() scope}, etc.<br>
* This is useful for advanced features and tools such as service clients and exception facades, code-generators,
* static-code-analyzers (SonarQube plugin), etc.
*
* @see #of(String)
* @see #of(Package)
* @see #of(Class)
*
* @author hohwille
*/
public final class OaspPackage {
/**
* The <a href="https://github.com/oasp/oasp4j/wiki/coding-conventions#packages">common "layer"</a> for cross-cutting
* code.
*/
public static final String LAYER_COMMON = "common";
/** The <a href="https://github.com/oasp/oasp4j/wiki/guide-dataaccess-layer">data-access layer</a>. */
public static final String LAYER_DATA_ACCESS = "dataaccess";
/** The <a href="https://github.com/oasp/oasp4j/wiki/guide-logic-layer">logic layer</a>. */
public static final String LAYER_LOGIC = "logic";
/** The <a href="https://github.com/oasp/oasp4j/wiki/guide-service-layer">service layer</a>. */
public static final String LAYER_SERVICE = "service";
/** The <a href="https://github.com/oasp/oasp4j/wiki/guide-batch-layer">batch layer</a>. */
public static final String LAYER_BATCH = "batch";
/**
* The <a href="https://github.com/oasp/oasp4j/wiki/guide-client-layer">client layer</a>. Please note that OASP does
* not recommend to implement the client layer in Java.
*/
public static final String LAYER_CLIENT = "client";
/** The <a href="https://github.com/oasp/oasp4j/wiki/coding-conventions#packages">scope</a> for APIs. */
public static final String SCOPE_API = "api";
/**
* The <a href="https://github.com/oasp/oasp4j/wiki/coding-conventions#packages">scope</a> for reusable base
* implementations.
*/
public static final String SCOPE_BASE = "base";
/** The <a href="https://github.com/oasp/oasp4j/wiki/coding-conventions#packages">scope</a> for implementations. */
public static final String SCOPE_IMPL = "impl";
private static final Set<String> LAYERS = new HashSet<>(
Arrays.asList(LAYER_BATCH, LAYER_CLIENT, LAYER_COMMON, LAYER_DATA_ACCESS, LAYER_LOGIC, LAYER_SERVICE));
private static final Set<String> SCOPES = new HashSet<>(Arrays.asList(SCOPE_API, SCOPE_BASE, SCOPE_IMPL));
private static final String REGEX_PKG_SEPARATOR = "\\.";
private final String[] segments;
private final int scopeIndex;
private Boolean valid;
private transient String root;
private transient String detail;
private transient String pkg;
/**
* Der Konstruktor.
*
* @param segments - see {@link #getSegment(int)}.
* @param scope - see {@link #getScope()}.
*/
private OaspPackage(String pkg, String[] segments, String root, String detail, int scope) {
super();
Objects.requireNonNull(segments, "segments");
this.pkg = pkg;
for (int i = 0; i < segments.length; i++) {
if (!isValidSegment(segments[i])) {
throw new IllegalArgumentException("segments[" + i + "] = " + segments[i]);
}
}
this.root = root;
this.detail = detail;
this.segments = segments;
this.scopeIndex = scope;
}
private static boolean isValidSegment(String segment) {
if (segment == null) {
return false;
}
if (segment.isEmpty()) {
return false;
}
if (segment.indexOf('.') >= 0) {
return false;
}
return true;
}
/**
* @return the number of {@link #getSegment(int) package segments}.
*/
public int getSegmentCount() {
return this.segments.length;
}
/**
* @param index the position of the requested segment. A valid index is in the range from {@code 0} to <code>
* {@link #getSegmentCount()}-1</code>.
* @return the {@link Package} segment at the given index or {@code null} if the given index is invalid.
*/
public String getSegment(int index) {
if ((index >= 0) && (index < this.segments.length)) {
return this.segments[index];
}
return null;
}
/**
* @return {@code true} if this {@link OaspPackage} is a valid according to OASP
* <a href="https://github.com/oasp/oasp4j/wiki/coding-conventions#packages">package conventions"</a>,
* {@code false} otherwise.
*/
public boolean isValid() {
if (this.valid == null) {
this.valid = Boolean.valueOf(isValidInternal());
}
return this.valid;
}
private boolean isValidInternal() {
if (this.segments.length < 4) {
return false;
}
if (!isValidLayer()) {
return false;
}
if (!isValidScope()) {
return false;
}
return true;
}
/**
* @return {@code true} if the {@link #getScope() scope} is valid (one of the predefined scopes {@link #isScopeApi()
* api}, {@link #isScopeBase() base}, or {@link #isScopeImpl() impl}).
*/
public boolean isValidScope() {
return SCOPES.contains(getScope());
}
/**
* @return {@code true} if the {@link #getLayer() layer} is valid (one of the predefined scopes {@link #isLayerBatch()
* batch}, {@link #isLayerClient() client}, {@link #isLayerCommon() common}, {@link #isLayerDataAccess()
* dataaccess}, {@link #isLayerLogic() logic}, or {@link #isLayerService() service}).
*/
public boolean isValidLayer() {
return LAYERS.contains(getLayer());
}
/**
* @return the root-{@link Package} of the organization or IT project owning the code.
*/
public String getRoot() {
if (this.root == null) {
if (this.scopeIndex == -1) {
return this.pkg;
}
int appIndex = this.scopeIndex - 3;
if (appIndex <= 0) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < appIndex; i++) {
if (i > 0) {
sb.append('.');
}
sb.append(this.segments[i]);
}
this.root = sb.toString();
}
return this.root;
}
/**
* @return the technical name of the application or (micro-)service.
*/
public String getApplication() {
return getSegment(this.scopeIndex - 3);
}
/**
* @return the business component the code belongs to.
*/
public String getComponent() {
return getSegment(this.scopeIndex - 2);
}
/**
* @return the layer the code is assigned to.
*/
public String getLayer() {
return getSegment(this.scopeIndex - 1);
}
/**
* @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_COMMON}.
*/
public boolean isLayerCommon() {
return LAYER_COMMON.equals(getLayer());
}
/**
* @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_DATA_ACCESS}.
*/
public boolean isLayerDataAccess() {
return LAYER_DATA_ACCESS.equals(getLayer());
}
/**
* @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_LOGIC}.
*/
public boolean isLayerLogic() {
return LAYER_LOGIC.equals(getLayer());
}
/**
* @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_SERVICE}.
*/
public boolean isLayerService() {
return LAYER_SERVICE.equals(getLayer());
}
/**
* @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_BATCH}.
*/
public boolean isLayerBatch() {
return LAYER_BATCH.equals(getLayer());
}
/**
* @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_CLIENT}.
*/
public boolean isLayerClient() {
return LAYER_CLIENT.equals(getLayer());
}
/**
* @return scope the scope the code is assigned to.
*/
public String getScope() {
return getSegment(this.scopeIndex);
}
/**
* @return {@code true} if {@link #getScope() scope} is {@link #SCOPE_API}.
*/
public boolean isScopeApi() {
return SCOPE_API.equals(getScope());
}
/**
* @return {@code true} if {@link #getScope() scope} is {@link #SCOPE_BASE}.
*/
public boolean isScopeBase() {
return SCOPE_BASE.equals(getScope());
}
/**
* @return {@code true} if {@link #getScope() scope} is {@link #SCOPE_IMPL}.
*/
public boolean isScopeImpl() {
return SCOPE_IMPL.equals(getScope());
}
/**
* @return the optional detail. Can be a single segment or multiple segments separated with dot. May be {@code null}.
*/
public String getDetail() {
if (this.detail == null) {
if (this.scopeIndex < 3) {
return null;
}
this.detail = joinPackage(this.scopeIndex + 1);
}
return this.detail;
}
@Override
public int hashCode() {
return Objects.hash(this.segments, this.scopeIndex);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if ((obj == null) || (obj.getClass() != OaspPackage.class)) {
return false;
}
OaspPackage other = (OaspPackage) obj;
if (!Arrays.deepEquals(this.segments, other.segments)) {
return false;
}
if (this.scopeIndex != other.scopeIndex) {
return false;
}
return true;
}
@Override
public String toString() {
if (this.pkg == null) {
this.pkg = joinPackage(0);
}
return this.pkg;
}
private String joinPackage(int start) {
return joinPackage(start, this.segments.length);
}
private String joinPackage(int start, int end) {
if (start >= end) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = start; i < end; i++) {
if (i > start) {
sb.append('.');
}
sb.append(this.segments[i]);
}
return sb.toString();
}
/**
* @param root - see {@link #getRoot()}.
* @param application - see {@link #getApplication()}.
* @param component - see {@link #getComponent()}.
* @param layer - see {@link #getLayer()}.
* @param scope - see {@link #getScope()}.
* @param detail - see {@link #getDetail()}.
* @return the {@link OaspPackage} for the given parameters.
*/
public static OaspPackage of(String root, String application, String component, String layer, String scope,
String detail) {
String[] roots;
if (root == null) {
roots = new String[0];
} else {
roots = root.split(REGEX_PKG_SEPARATOR);
}
String[] details;
if (detail == null) {
details = new String[0];
} else {
details = detail.split(REGEX_PKG_SEPARATOR);
}
String[] segments = new String[roots.length + details.length + 4];
System.arraycopy(roots, 0, segments, 0, roots.length);
int i = roots.length;
segments[i++] = application;
segments[i++] = component;
segments[i++] = layer;
segments[i++] = scope;
System.arraycopy(details, 0, segments, i, details.length);
return new OaspPackage(null, segments, root, detail, (i - 1));
}
/**
* @param packageName the {@link Package#getName() package name} to parse.
* @return the parsed {@link OaspPackage} corresponding to the given package.
*/
public static OaspPackage of(String packageName) {
String[] segments = packageName.split(REGEX_PKG_SEPARATOR);
int scopeIndex = -1;
for (int i = 2; i < segments.length; i++) {
if (SCOPES.contains(segments[i])) {
scopeIndex = i;
break;
}
if (LAYERS.contains(segments[i])) {
scopeIndex = i + 1;
}
}
return new OaspPackage(packageName, segments, null, null, scopeIndex);
}
/**
* @param javaPackage the {@link Package} to parse.
* @return the parsed {@link OaspPackage} corresponding to the given package.
*/
public static OaspPackage of(Package javaPackage) {
return of(javaPackage.getName());
}
/**
* @param type the {@link Class} {@link Class#getPackage() located} in the {@link Package} to parse.
* @return the parsed {@link OaspPackage} corresponding to the {@link Package} {@link Class#getPackage() of} the given
* {@link Class}.
*/
public static OaspPackage of(Class<?> type) {
return of(type.getPackage());
}
}