Skip to content

Commit

Permalink
Add file path provider
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-m-knight-gs committed Feb 6, 2025
1 parent 48f97ae commit 0696d75
Show file tree
Hide file tree
Showing 6 changed files with 682 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2024 Goldman Sachs
//
// 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 org.finos.legend.pure.m3.serialization.compiler.file;

import org.finos.legend.pure.m3.serialization.compiler.ExtensibleSerializer;

import java.nio.file.FileSystems;
import java.util.Arrays;

public class FilePathProvider extends ExtensibleSerializer<FilePathProviderExtension>
{
private FilePathProvider(Iterable<? extends FilePathProviderExtension> extensions, int defaultVersion)
{
super(extensions, defaultVersion);
}

public String getElementFilePath(String elementPath)
{
return getElementFilePath(elementPath, null);
}

public String getElementFilePath(String elementPath, String fsSeparator)
{
return getElementFilePath(elementPath, fsSeparator, getDefaultExtension());
}

public String getElementFilePath(String elementPath, int version)
{
return getElementFilePath(elementPath, null, version);
}

public String getElementFilePath(String elementPath, String fsSeparator, int version)
{
return getElementFilePath(elementPath, fsSeparator, getExtension(version));
}

private String getElementFilePath(String elementPath, String fsSeparator, FilePathProviderExtension extension)
{
return extension.getElementFilePath(
validateNonEmpty(elementPath, "element path"),
resolveFSSeparator(fsSeparator));
}

public String getModuleMetadataFilePath(String moduleName)
{
return getModuleMetadataFilePath(moduleName, null);
}

public String getModuleMetadataFilePath(String moduleName, String fsSeparator)
{
return getModuleMetadataFilePath(moduleName, fsSeparator, getDefaultExtension());
}

public String getModuleMetadataFilePath(String moduleName, int version)
{
return getModuleMetadataFilePath(moduleName, null, version);
}

public String getModuleMetadataFilePath(String moduleName, String fsSeparator, int version)
{
return getModuleMetadataFilePath(moduleName, fsSeparator, getExtension(version));
}

private String getModuleMetadataFilePath(String moduleName, String fsSeparator, FilePathProviderExtension extension)
{
return extension.getModuleMetadataFilePath(
validateNonEmpty(moduleName, "module name"),
resolveFSSeparator(fsSeparator));
}

private static String validateNonEmpty(String string, String description)
{
if ((string == null) || string.isEmpty())
{
throw new IllegalArgumentException(description + " may not be null or empty");
}
return string;
}

private static String resolveFSSeparator(String fsSeparator)
{
return (fsSeparator == null) ? getDefaultFSSeparator() : fsSeparator;
}

private static String getDefaultFSSeparator()
{
return FileSystems.getDefault().getSeparator();
}

public static Builder builder()
{
return new Builder();
}

public static class Builder extends ExtensibleSerializer.AbstractBuilder<FilePathProviderExtension, FilePathProvider>
{
private Builder()
{
}

public Builder withExtension(FilePathProviderExtension extension)
{
addExtension(extension);
return this;
}

public Builder withExtensions(Iterable<? extends FilePathProviderExtension> extensions)
{
addExtensions(extensions);
return this;
}

public Builder withExtensions(FilePathProviderExtension... extensions)
{
return withExtensions(Arrays.asList(extensions));
}

public Builder withLoadedExtensions(ClassLoader classLoader)
{
loadExtensions(classLoader);
return this;
}

public Builder withLoadedExtensions()
{
loadExtensions();
return this;
}

public Builder withDefaultVersion(int defaultVersion)
{
setDefaultVersion(defaultVersion);
return this;
}

@Override
protected FilePathProvider build(Iterable<FilePathProviderExtension> extensions, int defaultVersion)
{
return new FilePathProvider(extensions, defaultVersion);
}

@Override
protected Class<FilePathProviderExtension> getExtensionClass()
{
return FilePathProviderExtension.class;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2024 Goldman Sachs
//
// 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 org.finos.legend.pure.m3.serialization.compiler.file;

import org.finos.legend.pure.m3.serialization.compiler.SerializerExtension;

public interface FilePathProviderExtension extends SerializerExtension
{
/**
* Get the relative file path for the binary file for the given element. This should be a relative file path, and
* must not start with the path separator. It should never be null or empty. Each name in the path should be no
* longer than 255 bytes when encoded in UTF-16.
*
* @param elementPath concrete element path
* @param fsSeparator filesystem path separator
* @return relative file path
*/
String getElementFilePath(String elementPath, String fsSeparator);

/**
* Get the relative file path for the metadata file for the given module. This should be a relative file path, and
* must not start with the path separator. Tt should never be null or empty. Each name in the path should be no
* longer than 255 bytes when encoded in UTF-16.
*
* @param moduleName module name
* @param fsSeparator filesystem path separator
* @return relative file path
*/
String getModuleMetadataFilePath(String moduleName, String fsSeparator);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2024 Goldman Sachs
//
// 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 org.finos.legend.pure.m3.serialization.compiler.file.v1;

import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.finos.legend.pure.m3.navigation.M3Paths;
import org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement;
import org.finos.legend.pure.m3.serialization.compiler.file.FilePathProviderExtension;

public class FilePathProviderExtensionV1 implements FilePathProviderExtension
{
// UTF-16 uses 2 bytes per Java char, possibly plus a byte order marker of 2 bytes
// Since the name must be <= 255 bytes in UTF-16, 126 is the length limit for names (126 * 2 + 2 = 254)
private static final int NAME_LEN_LIMIT = 126;
private static final int SUFFIX_LEN = 16;

private static final String ELEMENT_FILE_EXTENSION = ".pelt";

private static final ImmutableList<String> MODULE_FILE_DIR = Lists.immutable.with("legend", "pure", "module");
private static final String MODULE_FILE_EXTENSION = ".pmf";

@Override
public int version()
{
return 1;
}

@Override
public String getElementFilePath(String elementPath, String fsSeparator)
{
if (PackageableElement.DEFAULT_PATH_SEPARATOR.equals(elementPath))
{
return M3Paths.Root + ELEMENT_FILE_EXTENSION;
}

StringBuilder builder = new StringBuilder(elementPath.length() + ELEMENT_FILE_EXTENSION.length());
int start = 0;
int sepLen = PackageableElement.DEFAULT_PATH_SEPARATOR.length();
int end;
while ((end = elementPath.indexOf(PackageableElement.DEFAULT_PATH_SEPARATOR, start)) != -1)
{
appendName(builder, elementPath, start, end, null).append(fsSeparator);
start = end + sepLen;
}

return appendName(builder, elementPath, start, elementPath.length(), ELEMENT_FILE_EXTENSION).toString();
}

@Override
public String getModuleMetadataFilePath(String moduleName, String fsSeparator)
{
StringBuilder builder = new StringBuilder(moduleName.length() + MODULE_FILE_EXTENSION.length() + (MODULE_FILE_DIR.size() * fsSeparator.length()) + 16);
MODULE_FILE_DIR.forEach(name -> builder.append(name).append(fsSeparator));
return appendName(builder, moduleName, 0, moduleName.length(), MODULE_FILE_EXTENSION).toString();
}

private static StringBuilder appendName(StringBuilder builder, String string, int start, int end, String extension)
{
int extLen = (extension == null) ? 0 : extension.length();
int len = (end - start) + extLen;
if (len < NAME_LEN_LIMIT)
{
// append the name, possibly plus extension, as it's below the limit
builder.append(string, start, end);
return (extension == null) ? builder : builder.append(extension);
}

// if the name is too long, append an initial segment plus a fixed length suffix computed from the overage
int overageStart = start + NAME_LEN_LIMIT - SUFFIX_LEN - extLen;
if (Character.isLowSurrogate(string.charAt(overageStart)) && Character.isHighSurrogate(string.charAt(overageStart - 1)))
{
// avoid splitting the string in the middle of a supplementary pair
overageStart--;
}
builder.ensureCapacity(builder.length() + (overageStart - start) + SUFFIX_LEN + extLen);
builder.append(string, start, overageStart);
String suffix = getOverageSuffix(string, overageStart, end);
for (int i = suffix.length(); i < SUFFIX_LEN; i++)
{
builder.append('0');
}
builder.append(suffix);
return (extension == null) ? builder : builder.append(extension);
}

private static String getOverageSuffix(String string, int start, int end)
{
long value = 0;
for (int i = start, cp; i < end; i += Character.charCount(cp))
{
value = (31 * value) + (cp = string.codePointAt(i));
}
return Long.toHexString(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.finos.legend.pure.m3.serialization.compiler.file.v1.FilePathProviderExtensionV1
Loading

0 comments on commit 0696d75

Please sign in to comment.