diff --git a/base-third-project/base-third-step8/pom.xml b/base-third-project/base-third-step8/pom.xml index 5dc00f306..af1c0c7cc 100644 --- a/base-third-project/base-third-step8/pom.xml +++ b/base-third-project/base-third-step8/pom.xml @@ -7,7 +7,7 @@ com.fr.third base-third - ${revision} + 10.0-FEATURE-SNAPSHOT step8 diff --git a/fine-ehcache/src/main/java/com/fr/third/net/sf/ehcache/pool/builtin-sizeof.filter b/fine-ehcache/src/main/java/com/fr/third/net/sf/ehcache/pool/builtin-sizeof.filter index 5e51d45db..b3650bc46 100644 --- a/fine-ehcache/src/main/java/com/fr/third/net/sf/ehcache/pool/builtin-sizeof.filter +++ b/fine-ehcache/src/main/java/com/fr/third/net/sf/ehcache/pool/builtin-sizeof.filter @@ -22,6 +22,11 @@ com.fr.third.org.hibernate.cache.spi.QueryKey.tenantIdentifier com.fr.third.org.hibernate.engine.spi.TypedValue.type com.fr.third.org.hibernate.engine.spi.TypedValue.entityMode +# Hibernate 5.x Second Level Entity/Collection Caching +com.fr.third.org.hibernate.cache.internal.CacheKeyImplementation.type +com.fr.third.org.hibernate.cache.internal.CacheKeyImplementation.entityOrRoleName +com.fr.third.org.hibernate.cache.internal.CacheKeyImplementation.tenantId + # This TC internal field is shared amongst all clustered entries org.terracotta.cache.serialization.SerializedEntry.tcClazz com.tc.object.TCObjectSelfImpl.tcClazz diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/Dialect.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/Dialect.java index 4aa69b349..c86f19177 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/Dialect.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/Dialect.java @@ -2099,6 +2099,11 @@ public abstract class Dialect implements ConversionContext { return " add constraint " + constraintName + " primary key "; } + + public String getDropPrimaryKeyConstraintString(String constraintName) { + return " drop constraint " + constraintName; + } + /** * Does the database/driver have bug in deleting rows that refer to other rows being deleted in the same query? * diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/MySQLDialect.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/MySQLDialect.java index 81bb77cda..acee55f31 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/MySQLDialect.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/MySQLDialect.java @@ -22,6 +22,8 @@ import com.fr.third.org.hibernate.dialect.identity.MySQLIdentityColumnSupport; import com.fr.third.org.hibernate.dialect.pagination.AbstractLimitHandler; import com.fr.third.org.hibernate.dialect.pagination.LimitHandler; import com.fr.third.org.hibernate.dialect.pagination.LimitHelper; +import com.fr.third.org.hibernate.dialect.unique.MySQLUniqueDelegate; +import com.fr.third.org.hibernate.dialect.unique.UniqueDelegate; import com.fr.third.org.hibernate.engine.spi.RowSelection; import com.fr.third.org.hibernate.exception.LockAcquisitionException; import com.fr.third.org.hibernate.exception.LockTimeoutException; @@ -43,6 +45,8 @@ import com.fr.third.org.hibernate.type.StandardBasicTypes; @SuppressWarnings("deprecation") public class MySQLDialect extends Dialect { + private final UniqueDelegate uniqueDelegate; + private static final LimitHandler LIMIT_HANDLER = new AbstractLimitHandler() { @Override public String processSql(String sql, RowSelection selection) { @@ -196,6 +200,8 @@ public class MySQLDialect extends Dialect { getDefaultProperties().setProperty( Environment.MAX_FETCH_DEPTH, "2" ); getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE ); + + uniqueDelegate = new MySQLUniqueDelegate( this ); } protected void registerVarcharTypes() { @@ -234,6 +240,11 @@ public class MySQLDialect extends Dialect { ); } + @Override + public String getDropPrimaryKeyConstraintString(String constraintName) { + return " drop primary key "; + } + @Override public boolean supportsLimit() { return true; @@ -422,6 +433,11 @@ public class MySQLDialect extends Dialect { return ps.getResultSet(); } + @Override + public UniqueDelegate getUniqueDelegate() { + return uniqueDelegate; + } + @Override public boolean supportsRowValueConstructorSyntax() { return true; diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/DefaultUniqueDelegate.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/DefaultUniqueDelegate.java index 836e3b68e..36ffdb5ec 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/DefaultUniqueDelegate.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/DefaultUniqueDelegate.java @@ -85,7 +85,7 @@ public class DefaultUniqueDelegate implements UniqueDelegate { final StringBuilder buf = new StringBuilder( "alter table " ); buf.append( tableName ); - buf.append(" drop constraint " ); + buf.append(getDropUnique()); if ( dialect.supportsIfExistsBeforeConstraintName() ) { buf.append( "if exists " ); } @@ -96,4 +96,7 @@ public class DefaultUniqueDelegate implements UniqueDelegate { return buf.toString(); } + protected String getDropUnique(){ + return " drop constraint "; + } } diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/MySQLUniqueDelegate.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/MySQLUniqueDelegate.java new file mode 100644 index 000000000..caab6c8e5 --- /dev/null +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/dialect/unique/MySQLUniqueDelegate.java @@ -0,0 +1,23 @@ +package com.fr.third.org.hibernate.dialect.unique; + +import com.fr.third.org.hibernate.dialect.Dialect; + +/** + * @author Andrea Boriero + */ +public class MySQLUniqueDelegate extends DefaultUniqueDelegate { + + /** + * Constructs MySQLUniqueDelegate + * + * @param dialect The dialect for which we are handling unique constraints + */ + public MySQLUniqueDelegate(Dialect dialect) { + super( dialect ); + } + + @Override + protected String getDropUnique() { + return " drop index "; + } +} \ No newline at end of file diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/IndexInformationImpl.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/IndexInformationImpl.java index 81bc14363..fb33abaa4 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/IndexInformationImpl.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/IndexInformationImpl.java @@ -21,12 +21,19 @@ import com.fr.third.org.hibernate.tool.schema.spi.SchemaManagementException; public class IndexInformationImpl implements IndexInformation { private final Identifier indexIdentifier; private final List columnList; + private boolean unique; public IndexInformationImpl(Identifier indexIdentifier, List columnList) { this.indexIdentifier = indexIdentifier; this.columnList = columnList; } + public IndexInformationImpl(Identifier indexIdentifier, List columnList, boolean unique) { + this.indexIdentifier = indexIdentifier; + this.columnList = columnList; + this.unique = unique; + } + @Override public Identifier getIndexIdentifier() { return indexIdentifier; @@ -37,6 +44,11 @@ public class IndexInformationImpl implements IndexInformation { return columnList; } + @Override + public boolean isUnique() { + return unique; + } + public static Builder builder(Identifier indexIdentifier) { return new Builder( indexIdentifier ); } @@ -44,6 +56,7 @@ public class IndexInformationImpl implements IndexInformation { public static class Builder { private final Identifier indexIdentifier; private final List columnList = new ArrayList(); + private boolean unique; public Builder(Identifier indexIdentifier) { this.indexIdentifier = indexIdentifier; @@ -54,13 +67,18 @@ public class IndexInformationImpl implements IndexInformation { return this; } + public Builder setUnique(boolean unique) { + this.unique = unique; + return this; + } + public IndexInformationImpl build() { if ( columnList.isEmpty() ) { throw new SchemaManagementException( "Attempt to resolve JDBC metadata failed to find columns for index [" + indexIdentifier.getText() + "]" ); } - return new IndexInformationImpl( indexIdentifier, Collections.unmodifiableList( columnList ) ); + return new IndexInformationImpl( indexIdentifier, Collections.unmodifiableList( columnList ), unique); } } } diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java index e98aef411..b1dbb4e9d 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java @@ -710,6 +710,24 @@ public class InformationExtractorJdbcDatabaseMetaDataImpl implements Information } try { + //处理唯一键 + ResultSet uniqueKeyResult = extractionContext.getJdbcDatabaseMetaData().getIndexInfo(catalogFilter, schemaFilter, tableName.getTableName().getText(), true, true); + try { + while (uniqueKeyResult.next()) { + if ( uniqueKeyResult.getShort("TYPE") == DatabaseMetaData.tableIndexStatistic ) { + continue; + } + final Identifier indexIdentifier = DatabaseIdentifier.toIdentifier(uniqueKeyResult.getString("INDEX_NAME")); + IndexInformationImpl.Builder builder = builders.get( indexIdentifier ); + if ( builder == null ) { + builder = IndexInformationImpl.builder( indexIdentifier ).setUnique(true); + builders.put( indexIdentifier, builder ); + } + } + } finally { + uniqueKeyResult.close(); + } + ResultSet resultSet = extractionContext.getJdbcDatabaseMetaData().getIndexInfo( catalogFilter, schemaFilter, diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/spi/IndexInformation.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/spi/IndexInformation.java index b5caeb1cf..b9426cc96 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/spi/IndexInformation.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/extract/spi/IndexInformation.java @@ -30,4 +30,6 @@ public interface IndexInformation { * @return The columns */ public List getIndexedColumns(); + + boolean isUnique(); } diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java index e89288ed2..d3e452571 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/AbstractSchemaMigrator.java @@ -6,12 +6,6 @@ */ package com.fr.third.org.hibernate.tool.schema.internal; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - import com.fr.third.org.hibernate.boot.Metadata; import com.fr.third.org.hibernate.boot.model.naming.Identifier; import com.fr.third.org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; @@ -26,17 +20,21 @@ import com.fr.third.org.hibernate.engine.config.spi.StandardConverters; import com.fr.third.org.hibernate.engine.jdbc.internal.FormatStyle; import com.fr.third.org.hibernate.engine.jdbc.internal.Formatter; import com.fr.third.org.hibernate.internal.util.StringHelper; +import com.fr.third.org.hibernate.mapping.Column; import com.fr.third.org.hibernate.mapping.Constraint; import com.fr.third.org.hibernate.mapping.ForeignKey; import com.fr.third.org.hibernate.mapping.Index; +import com.fr.third.org.hibernate.mapping.PrimaryKey; import com.fr.third.org.hibernate.mapping.Table; import com.fr.third.org.hibernate.mapping.UniqueKey; import com.fr.third.org.hibernate.resource.transaction.spi.DdlTransactionIsolator; import com.fr.third.org.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy; +import com.fr.third.org.hibernate.tool.schema.extract.spi.ColumnInformation; import com.fr.third.org.hibernate.tool.schema.extract.spi.DatabaseInformation; import com.fr.third.org.hibernate.tool.schema.extract.spi.ForeignKeyInformation; import com.fr.third.org.hibernate.tool.schema.extract.spi.IndexInformation; import com.fr.third.org.hibernate.tool.schema.extract.spi.NameSpaceTablesInformation; +import com.fr.third.org.hibernate.tool.schema.extract.spi.PrimaryKeyInformation; import com.fr.third.org.hibernate.tool.schema.extract.spi.SequenceInformation; import com.fr.third.org.hibernate.tool.schema.extract.spi.TableInformation; import com.fr.third.org.hibernate.tool.schema.internal.exec.GenerationTarget; @@ -48,9 +46,15 @@ import com.fr.third.org.hibernate.tool.schema.spi.SchemaFilter; import com.fr.third.org.hibernate.tool.schema.spi.SchemaManagementException; import com.fr.third.org.hibernate.tool.schema.spi.SchemaMigrator; import com.fr.third.org.hibernate.tool.schema.spi.TargetDescriptor; - import com.fr.third.org.jboss.logging.Logger; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + import static com.fr.third.org.hibernate.cfg.AvailableSettings.UNIQUE_CONSTRAINT_SCHEMA_UPDATE_STRATEGY; /** @@ -355,6 +359,29 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator { if ( uniqueConstraintStrategy != UniqueConstraintSchemaUpdateStrategy.SKIP ) { final Exporter exporter = dialect.getUniqueKeyExporter(); + Set identifiers = getUniqueKeyIdentifiers(table); + if (tableInfo != null) { + for (IndexInformation indexInfo : tableInfo.getIndexes()) { + if (!indexInfo.isUnique() || indexInfo.getIndexIdentifier().equals(tableInfo.getPrimaryKey().getPrimaryKeyIdentifier())) { + continue; + } + //如果旧的唯一键已经不存在, 则删除 + if (!identifiers.contains(indexInfo.getIndexIdentifier())) { + log.warn("delete unique key not existed in entity: " + indexInfo.getIndexIdentifier().getText()); + UniqueKey old = new UniqueKey(); + old.setTable(table); + old.setName(indexInfo.getIndexIdentifier().getText()); + applySqlStrings( + true, + exporter.getSqlDropStrings(old, metadata), + formatter, + options, + targets + ); + } + } + } + final Iterator ukItr = table.getUniqueKeyIterator(); while ( ukItr.hasNext() ) { final UniqueKey uniqueKey = (UniqueKey) ukItr.next(); @@ -388,6 +415,18 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator { } } + private Set getUniqueKeyIdentifiers(Table table) { + Set identifiers = new HashSet<>(); + Iterator uniqueKeys = table.getUniqueKeyIterator(); + while (uniqueKeys.hasNext()) { + UniqueKey uniqueKey = uniqueKeys.next(); + if (StringHelper.isNotEmpty(uniqueKey.getName())) { + identifiers.add(Identifier.toIdentifier(uniqueKey.getName())); + } + } + return identifiers; + } + private UniqueConstraintSchemaUpdateStrategy determineUniqueConstraintSchemaUpdateStrategy(Metadata metadata) { final ConfigurationService cfgService = ((MetadataImplementor) metadata).getMetadataBuildingOptions() .getServiceRegistry() @@ -446,6 +485,57 @@ public abstract class AbstractSchemaMigrator implements SchemaMigrator { return tableInformation.getForeignKey( Identifier.toIdentifier( foreignKey.getName() ) ); } + protected void applyPrimaryKey( + Table table, + TableInformation tableInfo, + Dialect dialect, + Metadata metadata, + Formatter formatter, + ExecutionOptions options, + GenerationTarget... targets) { + try { + PrimaryKey primaryKey = table.getPrimaryKey(); + if (primaryKey == null) { + return; + } + Exporter exporter = new StandardPrimaryKeyExporter(dialect); + PrimaryKeyInformation existedPrimaryKeyInfo = tableInfo.getPrimaryKey(); + if (existedPrimaryKeyInfo == null) { + applySqlStrings(true, exporter.getSqlCreateStrings(primaryKey, metadata), formatter, options, targets); + } else if (!columnEquals(table.getPrimaryKey().getColumns(), existedPrimaryKeyInfo.getColumns())) { + //主键列不匹配, 先删再增 + PrimaryKey deletedKey = copyPrimaryKey(tableInfo.getPrimaryKey().getPrimaryKeyIdentifier().getText(), table); + applySqlStrings(true, exporter.getSqlDropStrings(deletedKey, metadata), formatter, options, targets); + applySqlStrings(true, exporter.getSqlCreateStrings(primaryKey, metadata), formatter, options, targets); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + } + + private PrimaryKey copyPrimaryKey(String newName, Table table) { + PrimaryKey copy = new PrimaryKey(table); + copy.setName(newName); + return copy; + } + + private boolean columnEquals(List columns, Iterable iterator) { + int count = 0; + Set names = new HashSet(); + for (Column column: columns) { + names.add(column.getName().toLowerCase()); + } + for (ColumnInformation columnInformation : iterator) { + String name = columnInformation.getColumnIdentifier().getText(); + if (!names.contains(name.toLowerCase())) { + return false; + } + count++; + } + return count == columns.size(); + } + protected void checkExportIdentifier(Exportable exportable, Set exportIdentifiers) { final String exportIdentifier = exportable.getExportIdentifier(); if ( exportIdentifiers.contains( exportIdentifier ) ) { diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java index 14b07629a..7d397a377 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/GroupedSchemaMigratorImpl.java @@ -83,6 +83,9 @@ public class GroupedSchemaMigratorImpl extends AbstractSchemaMigrator { if ( tableInformation == null || ( tableInformation != null && tableInformation.isPhysicalTable() ) ) { applyIndexes( table, tableInformation, dialect, metadata, formatter, options, targets ); applyUniqueKeys( table, tableInformation, dialect, metadata, formatter, options, targets ); + if (tableInformation != null) { + applyPrimaryKey(table, tableInformation, dialect, metadata, formatter, options, targets); + } } } } diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java index 716623bb6..154b20f63 100644 --- a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/IndividuallySchemaMigratorImpl.java @@ -83,6 +83,9 @@ public class IndividuallySchemaMigratorImpl extends AbstractSchemaMigrator { if ( tableInformation == null || ( tableInformation != null && tableInformation.isPhysicalTable() ) ) { applyIndexes( table, tableInformation, dialect, metadata, formatter, options, targets ); applyUniqueKeys( table, tableInformation, dialect, metadata, formatter, options, targets ); + if (tableInformation != null) { + applyPrimaryKey(table, tableInformation, dialect, metadata, formatter, options, targets); + } } } } diff --git a/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/StandardPrimaryKeyExporter.java b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/StandardPrimaryKeyExporter.java new file mode 100644 index 000000000..4b9393023 --- /dev/null +++ b/fine-hibernate/src/main/java/com/fr/third/org/hibernate/tool/schema/internal/StandardPrimaryKeyExporter.java @@ -0,0 +1,63 @@ +package com.fr.third.org.hibernate.tool.schema.internal; + +import com.fr.third.org.hibernate.boot.Metadata; +import com.fr.third.org.hibernate.dialect.Dialect; +import com.fr.third.org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import com.fr.third.org.hibernate.mapping.Column; +import com.fr.third.org.hibernate.mapping.PrimaryKey; +import com.fr.third.org.hibernate.tool.schema.spi.Exporter; + +import java.util.Iterator; + +/** + * @author Elijah + * @version 10.5 + * Created by Elijah on 2020/9/30 + */ +public class StandardPrimaryKeyExporter implements Exporter { + private Dialect dialect; + + public StandardPrimaryKeyExporter(Dialect dialect) { + this.dialect = dialect; + } + + @Override + public String[] getSqlCreateStrings(PrimaryKey primaryKey, Metadata metadata) { + final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); + final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + primaryKey.getTable().getQualifiedTableName(), + dialect + ); + + final String constraintName = dialect.quote( primaryKey.getName() ); + return new String[]{ "alter table " + tableName + dialect.getAddPrimaryKeyConstraintString(constraintName) + " " + columnsToString( primaryKey ) }; + } + + protected String columnsToString(PrimaryKey primaryKey) { + final StringBuilder sb = new StringBuilder(); + sb.append( " (" ); + final Iterator columnIterator = primaryKey.columnIterator(); + while ( columnIterator.hasNext() ) { + final Column column = columnIterator.next(); + sb.append( column.getQuotedName( dialect ) ); + if ( columnIterator.hasNext() ) { + sb.append( ", " ); + } + } + + return sb.append( ')' ).toString(); + } + + @Override + public String[] getSqlDropStrings(PrimaryKey primaryKey, Metadata metadata) { + final JdbcEnvironment jdbcEnvironment = metadata.getDatabase().getJdbcEnvironment(); + final String tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format( + primaryKey.getTable().getQualifiedTableName(), + dialect + ); + final StringBuilder buf = new StringBuilder( "alter table " ); + buf.append( tableName ); + buf.append(dialect.getDropPrimaryKeyConstraintString(dialect.quote( primaryKey.getName()))); + return new String[] { buf.toString() }; + } +} \ No newline at end of file