Skip to content

Commit 0696d75

Browse files
Add file path provider
1 parent 48f97ae commit 0696d75

File tree

6 files changed

+682
-0
lines changed

6 files changed

+682
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright 2024 Goldman Sachs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.finos.legend.pure.m3.serialization.compiler.file;
16+
17+
import org.finos.legend.pure.m3.serialization.compiler.ExtensibleSerializer;
18+
19+
import java.nio.file.FileSystems;
20+
import java.util.Arrays;
21+
22+
public class FilePathProvider extends ExtensibleSerializer<FilePathProviderExtension>
23+
{
24+
private FilePathProvider(Iterable<? extends FilePathProviderExtension> extensions, int defaultVersion)
25+
{
26+
super(extensions, defaultVersion);
27+
}
28+
29+
public String getElementFilePath(String elementPath)
30+
{
31+
return getElementFilePath(elementPath, null);
32+
}
33+
34+
public String getElementFilePath(String elementPath, String fsSeparator)
35+
{
36+
return getElementFilePath(elementPath, fsSeparator, getDefaultExtension());
37+
}
38+
39+
public String getElementFilePath(String elementPath, int version)
40+
{
41+
return getElementFilePath(elementPath, null, version);
42+
}
43+
44+
public String getElementFilePath(String elementPath, String fsSeparator, int version)
45+
{
46+
return getElementFilePath(elementPath, fsSeparator, getExtension(version));
47+
}
48+
49+
private String getElementFilePath(String elementPath, String fsSeparator, FilePathProviderExtension extension)
50+
{
51+
return extension.getElementFilePath(
52+
validateNonEmpty(elementPath, "element path"),
53+
resolveFSSeparator(fsSeparator));
54+
}
55+
56+
public String getModuleMetadataFilePath(String moduleName)
57+
{
58+
return getModuleMetadataFilePath(moduleName, null);
59+
}
60+
61+
public String getModuleMetadataFilePath(String moduleName, String fsSeparator)
62+
{
63+
return getModuleMetadataFilePath(moduleName, fsSeparator, getDefaultExtension());
64+
}
65+
66+
public String getModuleMetadataFilePath(String moduleName, int version)
67+
{
68+
return getModuleMetadataFilePath(moduleName, null, version);
69+
}
70+
71+
public String getModuleMetadataFilePath(String moduleName, String fsSeparator, int version)
72+
{
73+
return getModuleMetadataFilePath(moduleName, fsSeparator, getExtension(version));
74+
}
75+
76+
private String getModuleMetadataFilePath(String moduleName, String fsSeparator, FilePathProviderExtension extension)
77+
{
78+
return extension.getModuleMetadataFilePath(
79+
validateNonEmpty(moduleName, "module name"),
80+
resolveFSSeparator(fsSeparator));
81+
}
82+
83+
private static String validateNonEmpty(String string, String description)
84+
{
85+
if ((string == null) || string.isEmpty())
86+
{
87+
throw new IllegalArgumentException(description + " may not be null or empty");
88+
}
89+
return string;
90+
}
91+
92+
private static String resolveFSSeparator(String fsSeparator)
93+
{
94+
return (fsSeparator == null) ? getDefaultFSSeparator() : fsSeparator;
95+
}
96+
97+
private static String getDefaultFSSeparator()
98+
{
99+
return FileSystems.getDefault().getSeparator();
100+
}
101+
102+
public static Builder builder()
103+
{
104+
return new Builder();
105+
}
106+
107+
public static class Builder extends ExtensibleSerializer.AbstractBuilder<FilePathProviderExtension, FilePathProvider>
108+
{
109+
private Builder()
110+
{
111+
}
112+
113+
public Builder withExtension(FilePathProviderExtension extension)
114+
{
115+
addExtension(extension);
116+
return this;
117+
}
118+
119+
public Builder withExtensions(Iterable<? extends FilePathProviderExtension> extensions)
120+
{
121+
addExtensions(extensions);
122+
return this;
123+
}
124+
125+
public Builder withExtensions(FilePathProviderExtension... extensions)
126+
{
127+
return withExtensions(Arrays.asList(extensions));
128+
}
129+
130+
public Builder withLoadedExtensions(ClassLoader classLoader)
131+
{
132+
loadExtensions(classLoader);
133+
return this;
134+
}
135+
136+
public Builder withLoadedExtensions()
137+
{
138+
loadExtensions();
139+
return this;
140+
}
141+
142+
public Builder withDefaultVersion(int defaultVersion)
143+
{
144+
setDefaultVersion(defaultVersion);
145+
return this;
146+
}
147+
148+
@Override
149+
protected FilePathProvider build(Iterable<FilePathProviderExtension> extensions, int defaultVersion)
150+
{
151+
return new FilePathProvider(extensions, defaultVersion);
152+
}
153+
154+
@Override
155+
protected Class<FilePathProviderExtension> getExtensionClass()
156+
{
157+
return FilePathProviderExtension.class;
158+
}
159+
}
160+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Goldman Sachs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.finos.legend.pure.m3.serialization.compiler.file;
16+
17+
import org.finos.legend.pure.m3.serialization.compiler.SerializerExtension;
18+
19+
public interface FilePathProviderExtension extends SerializerExtension
20+
{
21+
/**
22+
* Get the relative file path for the binary file for the given element. This should be a relative file path, and
23+
* must not start with the path separator. It should never be null or empty. Each name in the path should be no
24+
* longer than 255 bytes when encoded in UTF-16.
25+
*
26+
* @param elementPath concrete element path
27+
* @param fsSeparator filesystem path separator
28+
* @return relative file path
29+
*/
30+
String getElementFilePath(String elementPath, String fsSeparator);
31+
32+
/**
33+
* Get the relative file path for the metadata file for the given module. This should be a relative file path, and
34+
* must not start with the path separator. Tt should never be null or empty. Each name in the path should be no
35+
* longer than 255 bytes when encoded in UTF-16.
36+
*
37+
* @param moduleName module name
38+
* @param fsSeparator filesystem path separator
39+
* @return relative file path
40+
*/
41+
String getModuleMetadataFilePath(String moduleName, String fsSeparator);
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2024 Goldman Sachs
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package org.finos.legend.pure.m3.serialization.compiler.file.v1;
16+
17+
import org.eclipse.collections.api.factory.Lists;
18+
import org.eclipse.collections.api.list.ImmutableList;
19+
import org.finos.legend.pure.m3.navigation.M3Paths;
20+
import org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement;
21+
import org.finos.legend.pure.m3.serialization.compiler.file.FilePathProviderExtension;
22+
23+
public class FilePathProviderExtensionV1 implements FilePathProviderExtension
24+
{
25+
// UTF-16 uses 2 bytes per Java char, possibly plus a byte order marker of 2 bytes
26+
// Since the name must be <= 255 bytes in UTF-16, 126 is the length limit for names (126 * 2 + 2 = 254)
27+
private static final int NAME_LEN_LIMIT = 126;
28+
private static final int SUFFIX_LEN = 16;
29+
30+
private static final String ELEMENT_FILE_EXTENSION = ".pelt";
31+
32+
private static final ImmutableList<String> MODULE_FILE_DIR = Lists.immutable.with("legend", "pure", "module");
33+
private static final String MODULE_FILE_EXTENSION = ".pmf";
34+
35+
@Override
36+
public int version()
37+
{
38+
return 1;
39+
}
40+
41+
@Override
42+
public String getElementFilePath(String elementPath, String fsSeparator)
43+
{
44+
if (PackageableElement.DEFAULT_PATH_SEPARATOR.equals(elementPath))
45+
{
46+
return M3Paths.Root + ELEMENT_FILE_EXTENSION;
47+
}
48+
49+
StringBuilder builder = new StringBuilder(elementPath.length() + ELEMENT_FILE_EXTENSION.length());
50+
int start = 0;
51+
int sepLen = PackageableElement.DEFAULT_PATH_SEPARATOR.length();
52+
int end;
53+
while ((end = elementPath.indexOf(PackageableElement.DEFAULT_PATH_SEPARATOR, start)) != -1)
54+
{
55+
appendName(builder, elementPath, start, end, null).append(fsSeparator);
56+
start = end + sepLen;
57+
}
58+
59+
return appendName(builder, elementPath, start, elementPath.length(), ELEMENT_FILE_EXTENSION).toString();
60+
}
61+
62+
@Override
63+
public String getModuleMetadataFilePath(String moduleName, String fsSeparator)
64+
{
65+
StringBuilder builder = new StringBuilder(moduleName.length() + MODULE_FILE_EXTENSION.length() + (MODULE_FILE_DIR.size() * fsSeparator.length()) + 16);
66+
MODULE_FILE_DIR.forEach(name -> builder.append(name).append(fsSeparator));
67+
return appendName(builder, moduleName, 0, moduleName.length(), MODULE_FILE_EXTENSION).toString();
68+
}
69+
70+
private static StringBuilder appendName(StringBuilder builder, String string, int start, int end, String extension)
71+
{
72+
int extLen = (extension == null) ? 0 : extension.length();
73+
int len = (end - start) + extLen;
74+
if (len < NAME_LEN_LIMIT)
75+
{
76+
// append the name, possibly plus extension, as it's below the limit
77+
builder.append(string, start, end);
78+
return (extension == null) ? builder : builder.append(extension);
79+
}
80+
81+
// if the name is too long, append an initial segment plus a fixed length suffix computed from the overage
82+
int overageStart = start + NAME_LEN_LIMIT - SUFFIX_LEN - extLen;
83+
if (Character.isLowSurrogate(string.charAt(overageStart)) && Character.isHighSurrogate(string.charAt(overageStart - 1)))
84+
{
85+
// avoid splitting the string in the middle of a supplementary pair
86+
overageStart--;
87+
}
88+
builder.ensureCapacity(builder.length() + (overageStart - start) + SUFFIX_LEN + extLen);
89+
builder.append(string, start, overageStart);
90+
String suffix = getOverageSuffix(string, overageStart, end);
91+
for (int i = suffix.length(); i < SUFFIX_LEN; i++)
92+
{
93+
builder.append('0');
94+
}
95+
builder.append(suffix);
96+
return (extension == null) ? builder : builder.append(extension);
97+
}
98+
99+
private static String getOverageSuffix(String string, int start, int end)
100+
{
101+
long value = 0;
102+
for (int i = start, cp; i < end; i += Character.charCount(cp))
103+
{
104+
value = (31 * value) + (cp = string.codePointAt(i));
105+
}
106+
return Long.toHexString(value);
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.finos.legend.pure.m3.serialization.compiler.file.v1.FilePathProviderExtensionV1

0 commit comments

Comments
 (0)